diff --git a/packages/syncfusion_flutter_barcodes/CHANGELOG.md b/packages/syncfusion_flutter_barcodes/CHANGELOG.md index aae94c492..68cb2ce89 100644 --- a/packages/syncfusion_flutter_barcodes/CHANGELOG.md +++ b/packages/syncfusion_flutter_barcodes/CHANGELOG.md @@ -1,7 +1,11 @@ ## Unreleased -**Bugs** +**General** +* Provided th​e Material 3 themes support. + +## [20.2.38] - 07/12/2022 +**Bugs** * #FB45676 - Now, the QR code generated for all kinds of the input values with 07 [codeVersion](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/codeVersion.html), medium [errorCorrectionLevel](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/errorCorrectionLevel.html), and alphaNumeric [inputMode](https://pub.dev/documentation/syncfusion_flutter_barcodes/latest/barcodes/QRCode/inputMode.html) will be scannable. ## [22.1.36] 06/28/2023 diff --git a/packages/syncfusion_flutter_barcodes/example/lib/main.dart b/packages/syncfusion_flutter_barcodes/example/lib/main.dart index a722cb97d..8d333b1eb 100644 --- a/packages/syncfusion_flutter_barcodes/example/lib/main.dart +++ b/packages/syncfusion_flutter_barcodes/example/lib/main.dart @@ -10,9 +10,6 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData( - useMaterial3: false, - ), home: Scaffold( appBar: AppBar( title: const Text('Barcode Generator Demo'), diff --git a/packages/syncfusion_flutter_barcodes/pubspec.yaml b/packages/syncfusion_flutter_barcodes/pubspec.yaml index 5c2771960..69ed80681 100644 --- a/packages/syncfusion_flutter_barcodes/pubspec.yaml +++ b/packages/syncfusion_flutter_barcodes/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_barcodes description: Flutter Barcodes generator library is used to generate and display data in the machine-readable, industry-standard 1D and 2D barcodes. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_barcodes environment: diff --git a/packages/syncfusion_flutter_calendar/CHANGELOG.md b/packages/syncfusion_flutter_calendar/CHANGELOG.md index e16167fb8..6d317dcfb 100644 --- a/packages/syncfusion_flutter_calendar/CHANGELOG.md +++ b/packages/syncfusion_flutter_calendar/CHANGELOG.md @@ -1,3 +1,16 @@ +## Unreleased + +**General** +* Provided th​e Material 3 themes support. + +**Bug fixes** +* \#FB50948 - Now, the 'setState() or markNeedsBuild() called during the build' exception will not be thrown when tapping today's button after swiping the `timelineMonth` view. +* \#FB50846 - Now, text size remains consistent when the app state or themes gets changed. + +## [24.1.46] - 17/01/2024 +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + ## [22.2.5] **Features** * Provided support to accessibility for builders in the Flutter event calendar. diff --git a/packages/syncfusion_flutter_calendar/example/lib/main.dart b/packages/syncfusion_flutter_calendar/example/lib/main.dart index 142eee8a0..7ca5d9b26 100644 --- a/packages/syncfusion_flutter_calendar/example/lib/main.dart +++ b/packages/syncfusion_flutter_calendar/example/lib/main.dart @@ -9,11 +9,7 @@ void main() { class CalendarApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Calendar Demo', - theme: ThemeData(useMaterial3: false), - home: const MyHomePage(), - ); + return const MaterialApp(title: 'Calendar Demo', home: MyHomePage()); } } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart index 11c013eb6..fdc7028eb 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment.dart @@ -75,7 +75,7 @@ class Appointment with Diagnosticable { this.endTimeZone, this.recurrenceRule, this.isAllDay = false, - this.notes, + String? notes, this.location, this.resourceIds, this.recurrenceId, @@ -85,12 +85,17 @@ class Appointment with Diagnosticable { this.subject = '', this.color = Colors.lightBlue, this.recurrenceExceptionDates, - }) { + }) : notes = notes != null && notes.contains('isOccurrenceAppointment') + ? notes.replaceAll('isOccurrenceAppointment', '') + : notes, + _notes = notes { recurrenceRule = recurrenceId != null ? null : recurrenceRule; _appointmentType = _getAppointmentType(); id = id ?? hashCode; } + String? _notes; + /// The start time for an [Appointment] in [SfCalendar]. /// /// Defaults to `DateTime.now()`. @@ -937,8 +942,8 @@ class Appointment with Diagnosticable { if (recurrenceId != null) { return AppointmentType.changedOccurrence; } else if (recurrenceRule != null && recurrenceRule!.isNotEmpty) { - if (notes != null && notes!.contains('isOccurrenceAppointment')) { - notes = notes!.replaceAll('isOccurrenceAppointment', ''); + if (_notes != null && _notes!.contains('isOccurrenceAppointment')) { + _notes = _notes!.replaceAll('isOccurrenceAppointment', ''); return AppointmentType.occurrence; } @@ -949,7 +954,7 @@ class Appointment with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart index 412938573..171277f9c 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_properties.dart @@ -689,7 +689,7 @@ class RecurrenceProperties with Diagnosticable { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart index 590993161..816710f71 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/agenda_view_layout.dart @@ -875,7 +875,7 @@ class _AgendaViewRenderObject extends CustomCalendarRenderObject { : themeData.textTheme.bodyMedium! .copyWith( color: isLargerScheduleUI && - calendarTheme.brightness == Brightness.light + themeData.brightness == Brightness.light ? Colors.black87 : Colors.white, fontSize: 13) diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart index f86e27ec6..d4ca633be 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/allday_appointment_layout.dart @@ -1393,7 +1393,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { appointmentView.startIndex <= index && appointmentView.endIndex > index) { selectionDecoration ??= BoxDecoration( - color: calendarTheme.brightness == Brightness.light + color: themeData.brightness == Brightness.light ? Colors.white.withOpacity(0.3) : Colors.black.withOpacity(0.4), border: diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart index fbdc86c1c..d838486f1 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/calendar_view_helper.dart @@ -741,13 +741,13 @@ class CalendarViewHelper { } /// Method to switch the views based on the keyboard interaction. - static KeyEventResult handleViewSwitchKeyBoardEvent(RawKeyEvent event, + static KeyEventResult handleViewSwitchKeyBoardEvent(KeyEvent event, CalendarController controller, List? allowedViews) { /// Ctrl + and Ctrl - used by browser to zoom the page, hence as referred /// EJ2 scheduler, we have used alt + numeric to switch between views in /// calendar web and windows CalendarView view = controller.view!; - if (event.isAltPressed) { + if (HardwareKeyboard.instance.isAltPressed) { if (event.logicalKey == LogicalKeyboardKey.digit1) { view = CalendarView.day; } else if (event.logicalKey == LogicalKeyboardKey.digit2) { @@ -1088,7 +1088,7 @@ class CalendarAppointment { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1258,7 +1258,7 @@ class CalendarTimeRegion { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart index 5f6c5085b..b807d9302 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/calendar_resource.dart @@ -243,7 +243,7 @@ class CalendarResource with Diagnosticable { final ImageProvider? image; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart index 101ffb110..c7f316e22 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/resource_view/resource_view.dart @@ -445,7 +445,7 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { if (mouseHoverPosition != null) { final Color resourceHoveringColor = - (calendarTheme.brightness == Brightness.dark + (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); @@ -476,11 +476,10 @@ class _ResourceViewRenderObject extends CustomCalendarRenderObject { : actualItemWidth / 2; final Color resourceCellBorderColor = cellBorderColor ?? calendarTheme.cellBorderColor!; - final Color resourceHoveringColor = - (calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); + final Color resourceHoveringColor = (themeData.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); final TextStyle displayNameTextStyle = calendarTheme.displayNameTextStyle!; _circlePainter.color = resourceCellBorderColor; _circlePainter.strokeWidth = 0.5; diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart index 7ed094732..50d6a066f 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart @@ -213,7 +213,7 @@ class DragAndDropSettings with Diagnosticable { final Duration autoNavigateDelay; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart index 2c78b4057..e87fd5c1b 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/header_style.dart @@ -115,7 +115,7 @@ class CalendarHeaderStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart index fb1bb18fd..1bfea896f 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/month_view_settings.dart @@ -617,7 +617,7 @@ class MonthViewSettings with Diagnosticable { final MonthNavigationDirection navigationDirection; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -982,7 +982,7 @@ class AgendaStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1552,7 +1552,7 @@ class MonthCellStyle with Diagnosticable { final Color? leadingDatesBackgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart index f52e7d9e1..f30b8bca5 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/resource_view_settings.dart @@ -222,7 +222,7 @@ class ResourceViewSettings with Diagnosticable { final bool showAvatar; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart index 6593c2011..c85f14ca8 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/schedule_view_settings.dart @@ -315,7 +315,7 @@ class ScheduleViewSettings with Diagnosticable { final bool hideEmptyScheduleWeek; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -597,7 +597,7 @@ class MonthHeaderSettings with Diagnosticable { final TextStyle? monthTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -915,7 +915,7 @@ class WeekHeaderSettings with Diagnosticable { final TextStyle? weekTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1152,7 +1152,7 @@ class DayHeaderSettings with Diagnosticable { final TextStyle? dateTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart index b4d126104..9cbc750ac 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_region.dart @@ -571,7 +571,7 @@ class TimeRegion with Diagnosticable { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart index 46303fa87..a4102ed83 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/time_slot_view_settings.dart @@ -693,7 +693,7 @@ class TimeSlotViewSettings with Diagnosticable { final int numberOfDaysInView; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart index b3b30836d..6eb8356ae 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/view_header_style.dart @@ -132,7 +132,7 @@ class ViewHeaderStyle with Diagnosticable { final TextStyle? dayTextStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart index 6cae76fa5..73fde62f2 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/week_number_style.dart @@ -81,7 +81,7 @@ class WeekNumberStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart index a5b874545..16b2245d1 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart @@ -36,6 +36,7 @@ import 'settings/time_region.dart'; import 'settings/time_slot_view_settings.dart'; import 'settings/view_header_style.dart'; import 'settings/week_number_style.dart'; +import 'theme.dart'; import 'views/calendar_view.dart'; /// Specifies the unconfirmed ripple animation duration used on custom splash. @@ -2815,7 +2816,7 @@ class _SfCalendarState extends State @override void didChangeDependencies() { - _textScaleFactor = MediaQuery.textScalerOf(context).scale(_textScaleFactor); + _textScaleFactor = MediaQuery.textScalerOf(context).scale(1); // default width value will be device width when the widget placed inside a // infinity width widget _minWidth = MediaQuery.of(context).size.width; @@ -3836,37 +3837,42 @@ class _SfCalendarState extends State SfCalendarThemeData _getThemeDataValue( SfCalendarThemeData calendarThemeData, ThemeData themeData) { final ColorScheme colorScheme = themeData.colorScheme; + final SfCalendarThemeData effectiveThemeData = themeData.useMaterial3 + ? SfCalendarThemeDataM3(context) + : SfCalendarThemeDataM2(context); + final bool isMaterial3 = themeData.useMaterial3; return calendarThemeData.copyWith( - brightness: calendarThemeData.brightness ?? colorScheme.brightness, - backgroundColor: - calendarThemeData.backgroundColor ?? Colors.transparent, - headerBackgroundColor: - calendarThemeData.headerBackgroundColor ?? Colors.transparent, - agendaBackgroundColor: - calendarThemeData.agendaBackgroundColor ?? Colors.transparent, + backgroundColor: calendarThemeData.backgroundColor ?? + effectiveThemeData.backgroundColor, + headerBackgroundColor: calendarThemeData.headerBackgroundColor ?? + effectiveThemeData.headerBackgroundColor, + agendaBackgroundColor: calendarThemeData.agendaBackgroundColor ?? + effectiveThemeData.agendaBackgroundColor, activeDatesBackgroundColor: - calendarThemeData.activeDatesBackgroundColor ?? Colors.transparent, - todayBackgroundColor: - calendarThemeData.todayBackgroundColor ?? Colors.transparent, + calendarThemeData.activeDatesBackgroundColor ?? + effectiveThemeData.activeDatesBackgroundColor, + todayBackgroundColor: calendarThemeData.todayBackgroundColor ?? + effectiveThemeData.todayBackgroundColor, trailingDatesBackgroundColor: calendarThemeData.trailingDatesBackgroundColor ?? - Colors.transparent, + effectiveThemeData.trailingDatesBackgroundColor, leadingDatesBackgroundColor: - calendarThemeData.leadingDatesBackgroundColor ?? Colors.transparent, + calendarThemeData.leadingDatesBackgroundColor ?? + effectiveThemeData.leadingDatesBackgroundColor, viewHeaderBackgroundColor: - calendarThemeData.viewHeaderBackgroundColor ?? Colors.transparent, - allDayPanelColor: - calendarThemeData.allDayPanelColor ?? Colors.transparent, - // ignore: lines_longer_than_80_chars + calendarThemeData.viewHeaderBackgroundColor ?? + effectiveThemeData.viewHeaderBackgroundColor, + allDayPanelColor: calendarThemeData.allDayPanelColor ?? + effectiveThemeData.allDayPanelColor, weekNumberBackgroundColor: calendarThemeData.weekNumberBackgroundColor ?? - colorScheme.onSurface.withOpacity(0.04), + effectiveThemeData.weekNumberBackgroundColor, cellBorderColor: calendarThemeData.cellBorderColor ?? - colorScheme.onSurface.withOpacity(0.16), - todayHighlightColor: - calendarThemeData.todayHighlightColor ?? colorScheme.primary, - selectionBorderColor: - calendarThemeData.selectionBorderColor ?? colorScheme.primary, + effectiveThemeData.cellBorderColor, + todayHighlightColor: calendarThemeData.todayHighlightColor ?? + effectiveThemeData.todayHighlightColor, + selectionBorderColor: calendarThemeData.selectionBorderColor ?? + effectiveThemeData.selectionBorderColor, blackoutDatesTextStyle: calendarThemeData.blackoutDatesTextStyle == null ? widget.blackoutDatesTextStyle : calendarThemeData.blackoutDatesTextStyle @@ -3874,7 +3880,7 @@ class _SfCalendarState extends State trailingDatesTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, + fontSize: isMaterial3 ? 14 : 13, ) .merge(calendarThemeData.trailingDatesTextStyle) .merge( @@ -3882,25 +3888,24 @@ class _SfCalendarState extends State leadingDatesTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 13, + fontSize: isMaterial3 ? 14 : 13, ) .merge(calendarThemeData.leadingDatesTextStyle) - .merge( - widget.monthViewSettings.monthCellStyle.leadingDatesTextStyle), + // ignore: lines_longer_than_80_chars + .merge(widget.monthViewSettings.monthCellStyle.leadingDatesTextStyle), todayTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onPrimary, - fontSize: 13, + fontSize: isMaterial3 ? 14 : 13, ) .merge(calendarThemeData.todayTextStyle) .merge(widget.todayTextStyle), headerTextStyle: themeData.textTheme.bodyLarge! .copyWith( color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 18, + fontSize: isMaterial3 ? 16 : 18, fontWeight: FontWeight.w400, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.headerTextStyle) .merge(widget.headerStyle.textStyle), activeDatesTextStyle: themeData.textTheme.bodyMedium! @@ -3908,32 +3913,28 @@ class _SfCalendarState extends State color: colorScheme.onSurface.withOpacity(0.87), fontSize: 13, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.activeDatesTextStyle) .merge(widget.monthViewSettings.monthCellStyle.textStyle), timeTextStyle: themeData.textTheme.bodySmall! .copyWith( color: colorScheme.onSurface.withOpacity(0.54), - fontSize: 10, + fontSize: isMaterial3 ? 12 : 10, fontWeight: FontWeight.w500, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.timeTextStyle) .merge(widget.timeSlotViewSettings.timeTextStyle), viewHeaderDateTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 15, + fontSize: isMaterial3 ? 14 : 15, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.viewHeaderDateTextStyle) .merge(widget.viewHeaderStyle.dateTextStyle), viewHeaderDayTextStyle: themeData.textTheme.bodySmall! .copyWith( color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 11, + fontSize: isMaterial3 ? 12 : 11, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.viewHeaderDayTextStyle) .merge(widget.viewHeaderStyle.dayTextStyle), displayNameTextStyle: themeData.textTheme.bodySmall! @@ -3942,15 +3943,13 @@ class _SfCalendarState extends State fontSize: 10, fontWeight: FontWeight.w500, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.displayNameTextStyle) .merge(widget.resourceViewSettings.displayNameTextStyle), weekNumberTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onSurface.withOpacity(0.87), - fontSize: 13, + fontSize: isMaterial3 ? 14 : 13, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.weekNumberTextStyle) .merge(widget.weekNumberStyle.textStyle), timeIndicatorTextStyle: themeData.textTheme.bodySmall! @@ -3959,7 +3958,6 @@ class _SfCalendarState extends State fontSize: 10, fontWeight: FontWeight.w500, ) - // ignore: lines_longer_than_80_chars .merge(calendarThemeData.timeIndicatorTextStyle) .merge(widget.dragAndDropSettings.timeIndicatorStyle)); } @@ -7063,9 +7061,9 @@ class _SfCalendarState extends State } } - return RawKeyboardListener( + return KeyboardListener( focusNode: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: Stack(children: [ Positioned( top: 0, @@ -8079,16 +8077,16 @@ class _SfCalendarState extends State })))); } - return RawKeyboardListener( + return KeyboardListener( focusNode: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: Stack(children: children), ); } /// Method to handle keyboard navigation for schedule view in calendar. - void _onKeyDown(RawKeyEvent event) { - if (event.runtimeType != RawKeyDownEvent) { + void _onKeyDown(KeyEvent event) { + if (event.runtimeType != KeyDownEvent) { return; } @@ -8328,7 +8326,7 @@ class _SfCalendarState extends State child: Container( padding: EdgeInsets.zero, decoration: BoxDecoration( - color: _calendarTheme.brightness == Brightness.dark + color: _themeData.brightness == Brightness.dark ? Colors.grey[850] : Colors.white, boxShadow: kElevationToShadow[6], @@ -8751,7 +8749,7 @@ class _SfCalendarState extends State padding: const EdgeInsets.all(5), decoration: _isMobilePlatform ? BoxDecoration( - color: _calendarTheme.brightness == Brightness.dark + color: _themeData.brightness == Brightness.dark ? Colors.grey[850] : Colors.white, boxShadow: const [ @@ -8762,7 +8760,7 @@ class _SfCalendarState extends State ], ) : BoxDecoration( - color: _calendarTheme.brightness == Brightness.dark + color: _themeData.brightness == Brightness.dark ? Colors.grey[850] : Colors.white, boxShadow: kElevationToShadow[6], @@ -10277,7 +10275,10 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { return; } - setState(() {}); + // To avoid the unnecessary build while the timeline month view building + SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { + setState(() {}); + }); } void _backward() { @@ -11390,7 +11391,7 @@ class _AgendaDateTimePainter extends CustomPainter { agendaDateNotifier.value!.hoveringOffset.dy) { _linePainter.color = isToday ? Colors.black.withOpacity(0.1) - : (calendarTheme.brightness == Brightness.dark + : (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); @@ -11456,7 +11457,7 @@ class _AgendaDateTimePainter extends CustomPainter { agendaDateNotifier.value!.hoveringOffset.dy) { _linePainter.color = isToday ? Colors.black.withOpacity(0.1) - : (calendarTheme.brightness == Brightness.dark + : (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart index cc160ebb6..ce205aa11 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart @@ -269,6 +269,7 @@ class _CustomCalendarScrollViewState extends State late ValueNotifier<_DragPaintDetails> _dragDetails; Offset? _dragDifferenceOffset; Timer? _timer; + double? _viewPortHeight; @override void initState() { @@ -495,6 +496,7 @@ class _CustomCalendarScrollViewState extends State @override Widget build(BuildContext context) { + _viewPortHeight = MediaQuery.of(context).size.height; if (!CalendarViewHelper.isTimelineView(widget.view) && widget.view != CalendarView.month) { _updateScrollPosition(); @@ -682,7 +684,7 @@ class _CustomCalendarScrollViewState extends State top: topPosition, child: FocusScope( node: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: isTimelineView ? Listener( onPointerSignal: _handlePointerSignal, @@ -2555,7 +2557,8 @@ class _CustomCalendarScrollViewState extends State /// Check the scrolling is vertical and timeline view does not have /// vertical scroll view then scroll the vertical movement on /// Horizontal direction. - if (event.scrollDelta.dy.abs() > event.scrollDelta.dx.abs() && + if (widget.height <= _viewPortHeight! && + event.scrollDelta.dy.abs() > event.scrollDelta.dx.abs() && viewKey._timelineViewVerticalScrollController!.position .maxScrollExtent == 0) { @@ -4197,7 +4200,7 @@ class _CustomCalendarScrollViewState extends State } DateTime? _updateSelectedDate( - RawKeyEvent event, + KeyEvent event, _CalendarViewState currentViewState, _CalendarView currentView, int resourceIndex, @@ -4339,7 +4342,7 @@ class _CustomCalendarScrollViewState extends State } /// Method to handle the page up/down key for timeslot views in calendar. - KeyEventResult _updatePageUpAndDown(RawKeyEvent event, + KeyEventResult _updatePageUpAndDown(KeyEvent event, _CalendarViewState currentViewState, bool isResourceEnabled) { if (widget.controller.view != CalendarView.day && widget.controller.view != CalendarView.week && @@ -4418,7 +4421,7 @@ class _CustomCalendarScrollViewState extends State /// Updates the appointment selection based on keyboard navigation in calendar KeyEventResult _updateAppointmentSelection( - RawKeyEvent event, + KeyEvent event, _CalendarViewState currentVisibleViewState, bool isResourceEnabled, AppointmentView? currentSelectedAppointment, @@ -4436,7 +4439,7 @@ class _CustomCalendarScrollViewState extends State _updateCalendarStateDetails.allDayAppointmentViewCollection; final List tempAppColl = isAllDay ? allDayAppointmentCollection : appointmentCollection; - if (event.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { if (event.logicalKey == LogicalKeyboardKey.tab) { if (currentAllDayAppointment != null || currentSelectedAppointment != null) { @@ -4487,7 +4490,7 @@ class _CustomCalendarScrollViewState extends State currentVisibleViewState, isAllDay, isResourceEnabled, - !event.isShiftPressed); + !HardwareKeyboard.instance.isShiftPressed); } } else if (event.logicalKey == LogicalKeyboardKey.tab) { if (currentAllDayAppointment != null || @@ -4535,7 +4538,7 @@ class _CustomCalendarScrollViewState extends State currentVisibleViewState, isAllDay, isResourceEnabled, - !event.isShiftPressed); + !HardwareKeyboard.instance.isShiftPressed); } return KeyEventResult.ignored; @@ -4674,15 +4677,16 @@ class _CustomCalendarScrollViewState extends State } } - KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { + KeyEventResult _onKeyDown(FocusNode node, KeyEvent event) { KeyEventResult result = KeyEventResult.ignored; - if (event.runtimeType != RawKeyDownEvent) { + if (event.runtimeType != KeyDownEvent) { return result; } widget.removePicker(); - if (event.isControlPressed && widget.view != CalendarView.schedule) { + if (HardwareKeyboard.instance.isControlPressed && + widget.view != CalendarView.schedule) { final bool canMoveToNextView = DateTimeHelper.canMoveToNextView( widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView, @@ -8302,6 +8306,7 @@ class _CalendarViewState extends State<_CalendarView> isRTL, widget.locale, widget.calendarTheme, + widget.themeData, widget.calendar.todayHighlightColor ?? widget.calendarTheme.todayHighlightColor, widget.calendar.todayTextStyle, @@ -8366,6 +8371,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.todayTextStyle, widget.calendar.cellBorderColor, widget.calendarTheme, + widget.themeData, _calendarCellNotifier, widget.calendar.monthViewSettings.showTrailingAndLeadingDates, widget.calendar.minDate, @@ -8521,6 +8527,7 @@ class _CalendarViewState extends State<_CalendarView> isRTL, widget.locale, widget.calendarTheme, + widget.themeData, widget.calendar.todayHighlightColor ?? widget.calendarTheme.todayHighlightColor, widget.calendar.todayTextStyle, @@ -9781,16 +9788,17 @@ class _CalendarViewState extends State<_CalendarView> (yPosition < allDayHeight - kAllDayAppointmentHeight || _updateCalendarStateDetails.allDayPanelHeight <= allDayHeight || appointmentView.position + 1 >= appointmentView.maxPositions)) { - if (!CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - appointmentView.appointment!.actualStartTime, - timeInterval) || - !CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - appointmentView.appointment!.actualEndTime, - timeInterval)) { + if ((!CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + appointmentView.appointment!.actualStartTime, + timeInterval) || + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + appointmentView.appointment!.actualEndTime, + timeInterval)) && + !appointmentView.appointment!.isSpanned) { return null; } if (selectedDate != null) { @@ -11368,6 +11376,7 @@ class _ViewHeaderViewPainter extends CustomPainter { this.isRTL, this.locale, this.calendarTheme, + this.themeData, this.todayHighlightColor, this.todayTextStyle, this.cellBorderColor, @@ -11389,6 +11398,7 @@ class _ViewHeaderViewPainter extends CustomPainter { final double timeLabelWidth; final double viewHeaderHeight; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final bool isRTL; final String locale; final Color? todayHighlightColor; @@ -11589,13 +11599,13 @@ class _ViewHeaderViewPainter extends CustomPainter { dayTextStyle = dayTextStyle.copyWith( color: dayTextStyle.color != null ? dayTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light + : themeData.brightness == Brightness.light ? Colors.black26 : Colors.white38); dateTextStyle = dateTextStyle.copyWith( color: dateTextStyle.color != null ? dateTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light + : themeData.brightness == Brightness.light ? Colors.black26 : Colors.white38); } @@ -11733,7 +11743,7 @@ class _ViewHeaderViewPainter extends CustomPainter { xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition, _dayTextPainter, - hoveringColor: (calendarTheme.brightness == Brightness.dark + hoveringColor: (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04)); @@ -11754,7 +11764,7 @@ class _ViewHeaderViewPainter extends CustomPainter { viewHeaderNotifier.value!.dx) { final Color hoveringColor = isToday ? Colors.black.withOpacity(0.12) - : (calendarTheme.brightness == Brightness.dark + : (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart index c431555af..4d99dccd1 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/day_view.dart @@ -829,7 +829,7 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; final TextStyle defaultTextStyle = TextStyle( - color: calendarTheme.brightness == Brightness.dark + color: themeData.brightness == Brightness.dark ? Colors.white54 : Colors.black45); for (int i = 0; i < count; i++) { diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart index 5a9398ef1..dc5cc5286 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/month_view.dart @@ -25,6 +25,7 @@ class MonthViewWidget extends StatefulWidget { this.todayTextStyle, this.cellBorderColor, this.calendarTheme, + this.themeData, this.calendarCellNotifier, this.showTrailingAndLeadingDates, this.minDate, @@ -64,6 +65,9 @@ class MonthViewWidget extends StatefulWidget { /// Holds the theme data details for calendar. final SfCalendarThemeData calendarTheme; + /// Holds the theme data for calendar. + final ThemeData themeData; + /// Holds the current hovering point used to paint the hovering. final ValueNotifier calendarCellNotifier; @@ -212,6 +216,7 @@ class _MonthViewWidgetState extends State { widget.todayTextStyle, widget.cellBorderColor, widget.calendarTheme, + widget.themeData, widget.calendarCellNotifier, widget.minDate, widget.maxDate, @@ -246,6 +251,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { this.todayTextStyle, this.cellBorderColor, this.calendarTheme, + this.themeData, this.calendarCellNotifier, this.minDate, this.maxDate, @@ -270,6 +276,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { final TextStyle? todayTextStyle; final Color? cellBorderColor; final SfCalendarThemeData calendarTheme; + final ThemeData themeData; final ValueNotifier calendarCellNotifier; final DateTime minDate; final DateTime maxDate; @@ -295,6 +302,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { todayTextStyle, cellBorderColor, calendarTheme, + themeData, calendarCellNotifier, minDate, maxDate, @@ -322,6 +330,7 @@ class _MonthViewRenderObjectWidget extends MultiChildRenderObjectWidget { ..todayTextStyle = todayTextStyle ..cellBorderColor = cellBorderColor ..calendarTheme = calendarTheme + ..themeData = themeData ..calendarCellNotifier = calendarCellNotifier ..minDate = minDate ..maxDate = maxDate @@ -348,6 +357,7 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { this._todayTextStyle, this._cellBorderColor, this._calendarTheme, + this._themeData, this._calendarCellNotifier, this._minDate, this._maxDate, @@ -566,7 +576,22 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { if (childCount != 0) { return; } + markNeedsPaint(); + } + ThemeData _themeData; + + ThemeData get themeData => _themeData; + + set themeData(ThemeData value) { + if (_themeData == value) { + return; + } + + _themeData = value; + if (childCount != 0) { + return; + } markNeedsPaint(); } @@ -906,7 +931,7 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final TextStyle disabledTextStyle = currentMonthTextStyle.copyWith( color: currentMonthTextStyle.color != null ? currentMonthTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light + : themeData.brightness == Brightness.light ? Colors.black26 : Colors.white38); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart index 5d1208168..fb4b26b50 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/views/timeline_view.dart @@ -1002,7 +1002,7 @@ class _TimelineRenderObject extends CustomCalendarRenderObject { _linePainter.style = PaintingStyle.fill; final int count = specialRegionBounds.length; final TextStyle defaultTextStyle = TextStyle( - color: calendarTheme.brightness == Brightness.dark + color: themeData.brightness == Brightness.dark ? Colors.white54 : Colors.black45); for (int i = 0; i < count; i++) { @@ -1302,13 +1302,13 @@ class TimelineViewHeaderView extends CustomPainter { dayTextStyle = dayTextStyle.copyWith( color: dayTextStyle.color != null ? dayTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light + : themeData.brightness == Brightness.light ? Colors.black26 : Colors.white38); dateTextStyle = dateTextStyle.copyWith( color: dateTextStyle.color != null ? dateTextStyle.color!.withOpacity(0.38) - : calendarTheme.brightness == Brightness.light + : themeData.brightness == Brightness.light ? Colors.black26 : Colors.white38); } @@ -1460,7 +1460,7 @@ class TimelineViewHeaderView extends CustomPainter { if (leftPosition + difference <= viewHeaderNotifier.value!.dx && rightPosition + difference >= viewHeaderNotifier.value!.dx && (size.height) - _padding >= viewHeaderNotifier.value!.dy) { - _hoverPainter.color = (calendarTheme.brightness == Brightness.dark + _hoverPainter.color = (themeData.brightness == Brightness.dark ? Colors.white : Colors.black87) .withOpacity(0.04); diff --git a/packages/syncfusion_flutter_calendar/pubspec.yaml b/packages/syncfusion_flutter_calendar/pubspec.yaml index 51d3de20f..b6f50a56e 100644 --- a/packages/syncfusion_flutter_calendar/pubspec.yaml +++ b/packages/syncfusion_flutter_calendar/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_calendar description: The Flutter Calendar widget has nine built-in configurable views that provide basic functionalities for scheduling and representing appointments/events efficiently. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_calendar screenshots: @@ -38,7 +38,7 @@ dependencies: syncfusion_flutter_datepicker: path: ../syncfusion_flutter_datepicker - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_charts/CHANGELOG.md b/packages/syncfusion_flutter_charts/CHANGELOG.md index 9c3505f2d..acdaee9d4 100644 --- a/packages/syncfusion_flutter_charts/CHANGELOG.md +++ b/packages/syncfusion_flutter_charts/CHANGELOG.md @@ -1,5 +1,287 @@ ## Unreleased +**General** + +* Provided th​e Material 3 themes support. + +### Features + +* Provided touch support for trackball builder. + +### Improvements: + +* Interaction performance has been improved by 2x for crosshair and trackball behavior. + +TrackballBehavior: + +* Improved the trackball label UI with the series name and y value instead of y value for XyDataSeries when setting the tooltip display mode as groupAllPoints. + +* Added below methods for pointer and gesture events, along with a paint method, to allow for customization of the trackball. + +* handleEvent +* handleLongPressStart +* handleLongPressMoveUpdate +* handleLongPressEnd +* handleTapDown +* handleTapUp +* handleDoubleTap +* handlePointerEnter +* handlePointerExit +* onPaint + +CrosshairBehavior: + +Added below methods for pointer and gesture events, along with a paint method, to allow for customization of the crosshair. + +* handleEvent +* handleLongPressStart +* handleLongPressMoveUpdate +* handleLongPressEnd +* handleTapDown +* handleTapUp +* handleDoubleTap +* handlePointerEnter +* handlePointerExit +* onPaint +* drawHorizontalAxisLine +* drawVerticalAxisLine +* drawHorizontalAxisTooltip +* drawVerticalAxisTooltip + +### Bug Fixes + +* Provided legend toggling animation support for [ColumnSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ColumnSeries-class.html) and [BarSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/BarSeries-class.html). +* Provided newly added datapoint animation support for [LineSeries](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/LineSeries-class.html) +* Provided data label intersection support for multiple series. +* \#FB50894 - Now, the stacked series is rendering properly while having multiple series with different x values. + +## [24.1.46] - 17/01/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.41] - 12/18/2023 + +### Features + +* Provided `onRendererCreated` callback support in the `ChartAxis`. +* Provided clipping support for annotation feature. +* Provided custom segment rendering support for Circular, Funnel, and Pyramid charts. +* Provided touch support for annotation content. + +## Improvements + +* Flutter charts rendering performance has been improved by 5x. +* Flutter charts memory usage has been reduced by 8x. +* Multi-level labels curly braces UI has been improved in ChartAxis. +* The `labelAlignment` property positioning has been improved in ChartAxis. +* Marker fill color is rendered based on themes. + +### Bug Fixes + +* \#FB48648 - Now, the line series will render correctly while having continuous empty points with drop mode. + +### Breaking changes + +Following breaking changes will occur in Charts. + +## CartesianChart: + +* The `series` property type has been changed to `List` from `List`. +* The `TechnicalIndicators` property has been renamed to `TechnicalIndicator`. + +## Axis: + +* The `visibleMinimum` property has been renamed to `initialVisibleMinimum`. +* The `visibleMaximum` property has been renamed to `initialVisibleMaximum`. +* The `zoomFactor` property has been renamed to `initialZoomFactor`. +* The `zoomPosition` property has been renamed to `initialZoomPosition`. +* The `minorTicksPerInterval` property is removed in both `CategoryAxis` and `DateTimeCategoryAxis`. +* The axis line now renders along with axis labels when the `placeLabelNearAxisLine` property is set to true. + +## Series: + +* The `isVisible` property has been renamed to `initialisVisible`. +* The `drawDataMarker` method arguments have been changed to `drawDataMarker(int index, Canvas canvas, Paint fillPaint, Paint strokePaint, Offset point, Size size, DataMarkerType type, [CartesianSeriesRenderer? seriesRenderer,])` in ChartSeries. +* The `calculateEmptyPointValue` method is removed from ChartSeries. +* Data label intersection does not work with multiple series. +* `ErrorBarSeries` has been changed to a single segmented series from multiple segments. + +## Selection: + +* The `initialSelectedIndex` will now select a single segment when the `enableMultiSelection` value is `false`. +* During the point selection, the single segment is now highlighted instead of the entire series in line-type series. + +## Legend: + +* The legend for `FunnelSeries` is now rendered according to the data source order. + +## ChartPointInfo: + +* The `seriesRendererDetails` and `seriesIndex` is removed in ChartPointInfo class. +* The `series` property type has been changed to `dynamic` from `CartesianSeries`. + +## CartesianChartPoint: + +The following properties has been removed from the `CartesianChartPoint` class. + +* yValue +* sortValue +* markerPoint +* markerPoint2 +* isEmpty +* isGap +* isDrop +* isVisible +* pointColorMapper +* dataLabelMapper +* region +* boxRectRegion +* outlierRegion +* outlierRegionPosition +* isIntermediateSum +* isTotalSum +* endValue +* originValue +* maxYValue +* labelRenderEvent +* isTooltipRenderEvent +* openPoint +* closePoint +* centerOpenPoint +* centerClosePoint +* lowPoint +* highPoint +* centerLowPoint +* centerHighPoint +* currentPoint +* startControl +* endControl +* highStartControl +* highEndControl +* lowStartControl +* lowEndControl +* minimumPoint +* maximumPoint +* lowerQuartilePoint +* upperQuartilePoint +* centerMinimumPoint +* centerMaximumPoint +* medianPoint +* centerMedianPoint +* centerMeanPoint +* originValueLeftPoint +* originValueRightPoint +* endValueLeftPoint +* endValueRightPoint +* horizontalPositiveErrorPoint +* horizontalNegativeErrorPoint +* verticalPositiveErrorPoint +* verticalNegativeErrorPoint +* errorBarValues +* outliersPoint +* controlPoint +* controlPointshigh +* controlPointslow +* regions +* cumulativeValue +* trackerRectRegion +* label +* label2 +* label3 +* label4 +* label5 +* outliersLabel +* labelFillRect +* labelFillRect2 +* labelFillRect3 +* labelFillRect4 +* labelFillRect5 +* outliersFillRect +* labelLocation +* labelLocation2 +* labelLocation3 +* labelLocation4 +* labelLocation5 +* outliersLocation +* dataLabelSaturationRegionInside +* dataLabelRegion +* dataLabelRegion2 +* dataLabelRegion3 +* dataLabelRegion4 +* dataLabelRegion5 +* outliersDataLabelRegion +* index +* overallDataPointIndex +* regionData +* visiblePointIndex + +## ChartPoint: + +The following properties has been removed from the `ChartPoint` class. Instead of this, you can get the values of these properties from the corresponding segment class. + +* degree +* startAngle +* endAngle +* midAngle +* center +* text +* fill +* strokeColor +* sortValue +* strokeWidth +* innerRadius +* outerRadius +* isExplode +* isShadow +* isEmpty +* isVisible +* isSelected +* dataLabelPosition +* renderPosition +* labelRect +* dataLabelSize +* saturationRegionOutside +* yRatio +* heightRatio +* radius +* pointColor +* trimmedText +* overflowTrimmedText +* isTooltipRenderEvent +* labelRenderEvent +* index +* shader + +## Behavior changes + +* Now, the axis line and labels are rendered above the series while using `crossesAt` feature in ChartAxis. +* The plot band `opacity` property is now applied to fill, stroke and text color. +* The series `opacity` property is now applied to the both series fill and stroke color. +* The tooltip `opacity` property is now applied to the fill, stroke, text and marker color. + +## Generic type changes + +The following classes are marked as a generic type. +* All chart series renderers +* All chart segments +* ChartSeriesController +* CircularSeriesController +* FunnelSeriesController +* PyramidSeriesController +* LegendItemBuilder +* ChartWidgetBuilder +* ChartDataLabelTemplateBuilder + +### Known issues + +* The bar type series legend toggling animation is not working. +* Newly added data point animation is not working. +* The axis size changes immediately when the axis range transitions from single digits to multiple digits, such as 2 (10) or 3 (100) digits. + +## [23.1.39] - 10/04/2023 + **Bugs** * #FB46807 - The tooltip showByIndex public method has been enhanced to work based on a given seriesIndex, even when multiple series are overlapped. @@ -7,7 +289,7 @@ * #FB46698 - Fixed the issue where the trackball tooltip markers overlapped the text when the tooltip text had different sizes. -## [22.2.9]- 08/15/2023 +## [22.2.9] - 08/15/2023 **Bugs** diff --git a/packages/syncfusion_flutter_charts/lib/charts.dart b/packages/syncfusion_flutter_charts/lib/charts.dart index d41f85692..1236aed94 100644 --- a/packages/syncfusion_flutter_charts/lib/charts.dart +++ b/packages/syncfusion_flutter_charts/lib/charts.dart @@ -40,6 +40,10 @@ export './src/charts/axis/multi_level_labels.dart' MultiLevelLabelStyle; export './src/charts/axis/numeric_axis.dart' hide RenderNumericAxis; export './src/charts/axis/plot_band.dart'; +export './src/charts/behaviors/crosshair.dart'; +export './src/charts/behaviors/trackball.dart' + show TrackballBehavior, TrackballMarkerSettings; +export './src/charts/behaviors/zooming.dart'; export './src/charts/cartesian_chart.dart'; export './src/charts/circular_chart.dart'; export './src/charts/common/chart_point.dart' @@ -126,21 +130,10 @@ export './src/charts/utils/typedef.dart' hide PointToPixelCallback; export 'src/charts/common/annotation.dart'; export 'src/charts/common/callbacks.dart' hide ErrorBarValues; export 'src/charts/common/title.dart'; -export 'src/charts/interactions/behavior.dart' - show ZoomPanBehavior, TrackballBehavior, CrosshairBehavior; //export user interaction export 'src/charts/interactions/selection.dart'; export 'src/charts/interactions/tooltip.dart' hide ChartTooltipInfo, TrendlineTooltipInfo; -export 'src/charts/interactions/trackball.dart' - hide - TrackballRenderObject, - TrackballInfo, - TrackballElement, - ChartLocation, - ChartTrackballInfo, - TooltipPositions, - ClosestPoints; export 'src/charts/trendline/trendline.dart' hide TrendlineWidget, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart index 800285fb8..5a92748f4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/axis.dart @@ -1690,7 +1690,7 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { _needsRangeUpdate = true; } - if (dependent.runtimeType.toString().contains('100')) { + if (dependent is Stacking100SeriesMixin) { _dependentIsStacked100 = true; } @@ -1816,6 +1816,7 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { ); } _needsRangeUpdate = false; + zoomingInProgress = false; } @override @@ -1864,8 +1865,10 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { !zoomingInProgress) { final DoubleRange autoScrollRange = updateAutoScrollingDelta( autoScrollingDelta!, newActualRange, newVisibleRange); - if (autoScrollingMode == AutoScrollingMode.end && - newActualRange.minimum < autoScrollRange.minimum) { + if ((autoScrollingMode == AutoScrollingMode.end && + newActualRange.minimum < autoScrollRange.minimum) || + (autoScrollingMode == AutoScrollingMode.start && + newActualRange.maximum > autoScrollRange.maximum)) { newVisibleRange = autoScrollRange; } } @@ -1912,8 +1915,10 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { !zoomingInProgress) { final DoubleRange autoScrollRange = updateAutoScrollingDelta( autoScrollingDelta!, newActualRange, newVisibleRange); - if (autoScrollingMode == AutoScrollingMode.end && - newActualRange.minimum < autoScrollRange.minimum) { + if ((autoScrollingMode == AutoScrollingMode.end && + newActualRange.minimum < autoScrollRange.minimum) || + (autoScrollingMode == AutoScrollingMode.start && + newActualRange.maximum > autoScrollRange.maximum)) { newVisibleRange = autoScrollRange; } } @@ -2238,7 +2243,8 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { void _calculateBorderPositions() { borderPositions.clear(); - final Color labelBorderColor = borderColor ?? chartThemeData!.axisLineColor; + final Color labelBorderColor = + (borderColor ?? chartThemeData!.axisLineColor)!; if (borderWidth > 0 && labelBorderColor != Colors.transparent) { switch (labelPlacement) { case LabelPlacement.onTicks: @@ -2708,6 +2714,10 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { length += isBetweenTicks ? 1 : 0; final int lastIndex = length - 1; for (int i = 0; i < length; i++) { + final double? position = visibleLabels[i].position; + if (position == null || position.isNaN) { + continue; + } num current; if (isBetweenTicks) { if (i < lastIndex) { @@ -2799,11 +2809,11 @@ abstract class RenderChartAxis extends RenderBox with ChartAreaUpdateMixin { if (visibleRange!.lies(current, end)) { final Rect frame = bounds(plotBand, current, end); addPlotBand(frame, plotBand); - current = plotBand.size != null - ? plotBandExtent(plotBand, current, - plotBand.isRepeatable ? plotBand.repeatEvery : end) - : end; } + current = plotBand.size != null + ? plotBandExtent(plotBand, current, + plotBand.isRepeatable ? plotBand.repeatEvery : end) + : end; return current; } @@ -3398,7 +3408,7 @@ class _HorizontalGridLineRenderer extends _GridLineRenderer { void _drawMajorGridLines(PaintingContext context, Offset offset) { final MajorGridLines majorGridLines = axis.majorGridLines; final Color color = - majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor; + (majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor)!; _drawGridLines(context, offset, axis.majorTickPositions, color, majorGridLines.width, majorGridLines.dashArray); } @@ -3407,7 +3417,7 @@ class _HorizontalGridLineRenderer extends _GridLineRenderer { void _drawMinorGridLines(PaintingContext context, Offset offset) { final MinorGridLines minorGridLines = axis.minorGridLines; final Color color = - minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor; + (minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor)!; _drawGridLines(context, offset, axis.minorTickPositions, color, minorGridLines.width, minorGridLines.dashArray); } @@ -3457,7 +3467,7 @@ class _VerticalGridLineRenderer extends _GridLineRenderer { void _drawMajorGridLines(PaintingContext context, Offset offset) { final MajorGridLines majorGridLines = axis.majorGridLines; final Color color = - majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor; + (majorGridLines.color ?? axis.chartThemeData!.majorGridLineColor)!; _drawGridLines(context, offset, axis.majorTickPositions, color, majorGridLines.width, majorGridLines.dashArray); } @@ -3466,7 +3476,7 @@ class _VerticalGridLineRenderer extends _GridLineRenderer { void _drawMinorGridLines(PaintingContext context, Offset offset) { final MinorGridLines minorGridLines = axis.minorGridLines; final Color color = - minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor; + (minorGridLines.color ?? axis.chartThemeData!.minorGridLineColor)!; _drawGridLines(context, offset, axis.minorTickPositions, color, minorGridLines.width, minorGridLines.dashArray); } @@ -4109,7 +4119,7 @@ class _HorizontalAxisRenderer extends _AxisRenderer { void _drawAxisLine(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = axis.axisLine.color ?? axis.chartThemeData!.axisLineColor + ..color = (axis.axisLine.color ?? axis.chartThemeData!.axisLineColor)! ..strokeWidth = axis.axisLine.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4127,8 +4137,8 @@ class _HorizontalAxisRenderer extends _AxisRenderer { void _drawMajorTicks(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = - axis.majorTickLines.color ?? axis.chartThemeData!.majorTickLineColor + ..color = (axis.majorTickLines.color ?? + axis.chartThemeData!.majorTickLineColor)! ..strokeWidth = axis.majorTickLines.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4144,8 +4154,8 @@ class _HorizontalAxisRenderer extends _AxisRenderer { void _drawMinorTicks(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = - axis.minorTickLines.color ?? axis.chartThemeData!.minorTickLineColor + ..color = (axis.minorTickLines.color ?? + axis.chartThemeData!.minorTickLineColor)! ..strokeWidth = axis.minorTickLines.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4175,7 +4185,7 @@ class _HorizontalAxisRenderer extends _AxisRenderer { final List axisLabels = axis.visibleLabels; for (final AxisLabel label in axisLabels) { - if (!label.isVisible || label.position == null) { + if (!label.isVisible || label.position == null || label.position!.isNaN) { continue; } @@ -4221,7 +4231,7 @@ class _HorizontalAxisRenderer extends _AxisRenderer { } final Color effectiveBorderColor = - axis.borderColor ?? axis.chartThemeData!.axisLineColor; + (axis.borderColor ?? axis.chartThemeData!.axisLineColor)!; if (effectiveBorderColor != Colors.transparent && axis.borderWidth > 0 && axis.borderPositions.isNotEmpty) { @@ -4261,8 +4271,8 @@ class _HorizontalAxisRenderer extends _AxisRenderer { final MultiLevelBorderType borderType = axis.multiLevelLabelStyle.borderType; final Paint paint = Paint() - ..color = axis.multiLevelLabelStyle.borderColor ?? - axis.chartThemeData!.axisLineColor + ..color = (axis.multiLevelLabelStyle.borderColor ?? + axis.chartThemeData!.axisLineColor)! ..strokeWidth = axis.multiLevelLabelStyle.borderWidth != 0 ? axis.multiLevelLabelStyle.borderWidth : axis.axisLine.width @@ -4382,7 +4392,7 @@ class _VerticalAxisRenderer extends _AxisRenderer { void _drawAxisLine(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = axis.axisLine.color ?? axis.chartThemeData!.axisLineColor + ..color = (axis.axisLine.color ?? axis.chartThemeData!.axisLineColor)! ..strokeWidth = axis.axisLine.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4400,8 +4410,8 @@ class _VerticalAxisRenderer extends _AxisRenderer { void _drawMajorTicks(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = - axis.majorTickLines.color ?? axis.chartThemeData!.majorTickLineColor + ..color = (axis.majorTickLines.color ?? + axis.chartThemeData!.majorTickLineColor)! ..strokeWidth = axis.majorTickLines.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4417,8 +4427,8 @@ class _VerticalAxisRenderer extends _AxisRenderer { void _drawMinorTicks(PaintingContext context, Offset offset) { final Paint paint = Paint() ..isAntiAlias = true - ..color = - axis.minorTickLines.color ?? axis.chartThemeData!.minorTickLineColor + ..color = (axis.minorTickLines.color ?? + axis.chartThemeData!.minorTickLineColor)! ..strokeWidth = axis.minorTickLines.width ..style = PaintingStyle.stroke; if (paint.color != Colors.transparent && paint.strokeWidth > 0) { @@ -4448,7 +4458,7 @@ class _VerticalAxisRenderer extends _AxisRenderer { final List axisLabels = axis.visibleLabels; for (final AxisLabel label in axisLabels) { - if (!label.isVisible || label.position == null) { + if (!label.isVisible || label.position == null || label.position!.isNaN) { continue; } @@ -4494,7 +4504,7 @@ class _VerticalAxisRenderer extends _AxisRenderer { } final Color effectiveBorderColor = - axis.borderColor ?? axis.chartThemeData!.axisLineColor; + (axis.borderColor ?? axis.chartThemeData!.axisLineColor)!; if (effectiveBorderColor != Colors.transparent && axis.borderWidth > 0 && axis.borderPositions.isNotEmpty) { @@ -4534,8 +4544,8 @@ class _VerticalAxisRenderer extends _AxisRenderer { final MultiLevelBorderType borderType = axis.multiLevelLabelStyle.borderType; final Paint paint = Paint() - ..color = axis.multiLevelLabelStyle.borderColor ?? - axis.chartThemeData!.axisLineColor + ..color = (axis.multiLevelLabelStyle.borderColor ?? + axis.chartThemeData!.axisLineColor)! ..strokeWidth = axis.multiLevelLabelStyle.borderWidth != 0 ? axis.multiLevelLabelStyle.borderWidth : axis.axisLine.width @@ -5689,6 +5699,8 @@ abstract class ChartAxisController { void _updateActualRange(DoubleRange range) { _actualRange = range; _onUpdateInitialZoomFactorAndPosition?.call(); + _isVisibleMinChanged = false; + _isVisibleMaxChanged = false; } void _updateZoomFactorAndPosition(num? min, num? max) { @@ -5800,7 +5812,7 @@ class CategoryAxisController extends ChartAxisController { double? get visibleMinimum => _visibleMin(); double? _visibleMinimum; set visibleMinimum(double? value) { - if (_visibleMinimum != value) { + if (visibleMinimum != value) { _visibleMinimum = value; _updateMinMaxIfNeeded(); _isVisibleMinChanged = true; @@ -5811,7 +5823,7 @@ class CategoryAxisController extends ChartAxisController { double? get visibleMaximum => _visibleMax(); double? _visibleMaximum; set visibleMaximum(double? value) { - if (_visibleMaximum != value) { + if (visibleMaximum != value) { _visibleMaximum = value; _updateMinMaxIfNeeded(); _isVisibleMaxChanged = true; @@ -5871,7 +5883,7 @@ class DateTimeAxisController extends ChartAxisController { DateTime? get visibleMinimum => _visibleMin(); DateTime? _visibleMinimum; set visibleMinimum(DateTime? value) { - if (_visibleMinimum != value) { + if (visibleMinimum != value) { _visibleMinimum = value; _updateMinMaxIfNeeded(); _isVisibleMinChanged = true; @@ -5882,7 +5894,7 @@ class DateTimeAxisController extends ChartAxisController { DateTime? get visibleMaximum => _visibleMax(); DateTime? _visibleMaximum; set visibleMaximum(DateTime? value) { - if (_visibleMaximum != value) { + if (visibleMaximum != value) { _visibleMaximum = value; _updateMinMaxIfNeeded(); _isVisibleMaxChanged = true; @@ -5950,7 +5962,7 @@ class DateTimeCategoryAxisController extends ChartAxisController { DateTime? get visibleMinimum => _visibleMin(); DateTime? _visibleMinimum; set visibleMinimum(DateTime? value) { - if (_visibleMinimum != value) { + if (visibleMinimum != value) { _visibleMinimum = value; if (_dateAxis.labels.isNotEmpty) { _updateMinMaxIfNeeded(); @@ -5964,7 +5976,7 @@ class DateTimeCategoryAxisController extends ChartAxisController { DateTime? get visibleMaximum => _visibleMax(); DateTime? _visibleMaximum; set visibleMaximum(DateTime? value) { - if (_visibleMaximum != value) { + if (visibleMaximum != value) { _visibleMaximum = value; if (_dateAxis.labels.isNotEmpty) { _updateMinMaxIfNeeded(); @@ -5980,7 +5992,7 @@ class DateTimeCategoryAxisController extends ChartAxisController { if (_actualRange != null) { return; } - super._updateActualRange(range); + _actualRange = range; if (_visibleMinimumIndex == null && _visibleMaximumIndex == null && _dateAxis.labels.isNotEmpty) { @@ -5996,8 +6008,12 @@ class DateTimeCategoryAxisController extends ChartAxisController { } } _updateMinMaxIfNeeded(); - _updateZoomFactorAndPosition(_visibleMinimumIndex, _visibleMaximumIndex); + if (_visibleMaximumIndex != null && _visibleMinimumIndex != null) { + _updateZoomFactorAndPosition( + _visibleMinimumIndex, _visibleMaximumIndex); + } } + _onUpdateInitialZoomFactorAndPosition?.call(); } void _updateMinMaxIfNeeded() { @@ -6073,7 +6089,7 @@ class LogarithmicAxisController extends ChartAxisController { double? get visibleMinimum => _visibleMin(); double? _visibleMinimum; set visibleMinimum(double? value) { - if (_visibleMinimum != value) { + if (visibleMinimum != value) { _visibleMinimum = value; _updateMinMaxIfNeeded(); _isVisibleMinChanged = true; @@ -6084,7 +6100,7 @@ class LogarithmicAxisController extends ChartAxisController { double? get visibleMaximum => _visibleMax(); double? _visibleMaximum; set visibleMaximum(double? value) { - if (_visibleMaximum != value) { + if (visibleMaximum != value) { _visibleMaximum = value; _updateMinMaxIfNeeded(); _isVisibleMaxChanged = true; @@ -6140,7 +6156,7 @@ class NumericAxisController extends ChartAxisController { double? get visibleMinimum => _visibleMin(); double? _visibleMinimum; set visibleMinimum(double? value) { - if (_visibleMinimum != value) { + if (visibleMinimum != value) { _visibleMinimum = value; _updateMinMaxIfNeeded(); _isVisibleMinChanged = true; @@ -6151,7 +6167,7 @@ class NumericAxisController extends ChartAxisController { double? get visibleMaximum => _visibleMax(); double? _visibleMaximum; set visibleMaximum(double? value) { - if (_visibleMaximum != value) { + if (visibleMaximum != value) { _visibleMaximum = value; _updateMinMaxIfNeeded(); _isVisibleMaxChanged = true; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart index 41a7910bf..f1cf6c2e5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/category_axis.dart @@ -468,7 +468,7 @@ class RenderCategoryAxis extends RenderChartAxis { @override void generateVisibleLabels() { hasTrimmedAxisLabel = false; - if (visibleRange == null) { + if (visibleRange == null || visibleInterval == 0) { return; } @@ -787,54 +787,46 @@ class RenderCategoryAxis extends RenderChartAxis { } void updateXValuesWithArrangeByIndex() { - final int seriesCount = dependents.length; if (!arrangeByIndex) { final List groupedRawValues = []; - if (seriesCount > 0) { - for (int i = 0; i < seriesCount; i++) { - final AxisDependent dependent = dependents[i]; - if (dependent is CartesianSeriesRenderer) { - final CartesianSeriesRenderer? series = - dependents[i] as CartesianSeriesRenderer?; - if (series != null && series.controller.isVisible) { - final List actualXValues = series.xRawValues; - final int length = actualXValues.length; - num? minimum; - num minValue = 0, maxValue = 0; - if (length > 0) { - for (int j = 0; j < length; j++) { - final String x = actualXValues[j].toString(); - if (!groupedRawValues.contains(x)) { - groupedRawValues.add(x); - } - - final List xValues = series.xValues; - xValues[j] = groupedRawValues.indexOf(x); - final num index = xValues[j]; - minimum ??= index; - minValue = min(minimum, index); - maxValue = max(minimum, index); - } - series.xMin = minValue; - series.xMax = maxValue; + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final List actualXValues = dependent.xRawValues; + final int length = actualXValues.length; + num? minimum; + num minValue = 0, maxValue = 0; + if (length > 0) { + for (int i = 0; i < length; i++) { + final String x = actualXValues[i].toString(); + if (!groupedRawValues.contains(x)) { + groupedRawValues.add(x); } + + final List xValues = dependent.xValues; + xValues[i] = groupedRawValues.indexOf(x); + final num index = xValues[i]; + minimum ??= index; + minValue = min(minimum, index); + maxValue = max(minimum, index); } + dependent.xMin = minValue; + dependent.xMax = maxValue; } } } } else { - if (seriesCount > 0) { - for (int i = 0; i < seriesCount; i++) { - final CartesianSeriesRenderer? series = - dependents[i] as CartesianSeriesRenderer?; - if (series != null && series.controller.isVisible) { - final int length = series.xRawValues.length; - if (length > 0) { - series.xValues.clear(); - series.xValues = List.generate(length, (int index) => index); - series.xMin = 0; - series.xMax = length - 1; + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final int length = dependent.dataCount; + if (length > 0) { + dependent.xValues.clear(); + for (int i = 0; i < length; i++) { + dependent.xValues.add(i); } + dependent.xMin = 0; + dependent.xMax = length - 1; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart index e9730dd36..f973572c2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_axis.dart @@ -953,7 +953,7 @@ class RenderDateTimeAxis extends RenderChartAxis { @override void generateVisibleLabels() { hasTrimmedAxisLabel = false; - if (visibleRange == null) { + if (visibleRange == null || visibleInterval == 0) { return; } @@ -973,8 +973,8 @@ class RenderDateTimeAxis extends RenderChartAxis { continue; } - final DateFormat niceDateFormat = - dateFormat ?? _niceDateFormat(current, previous.toInt()); + final DateFormat niceDateFormat = dateFormat ?? + dateTimeAxisLabelFormat(this, current, previous.toInt()); String text = niceDateFormat .format(DateTime.fromMillisecondsSinceEpoch(current.toInt())); if (labelFormat != null && labelFormat != '') { @@ -1022,6 +1022,9 @@ class RenderDateTimeAxis extends RenderChartAxis { } current = _nextDate(current, visibleInterval, visibleIntervalType) .millisecondsSinceEpoch; + if (previous == current) { + return; + } } super.generateVisibleLabels(); @@ -1151,73 +1154,6 @@ class RenderDateTimeAxis extends RenderChartAxis { return date; } - DateFormat _niceDateFormat([num? current, int? previous]) { - final bool notDoubleInterval = - (interval != null && interval! % 1 == 0) || interval == null; - switch (visibleIntervalType) { - case DateTimeIntervalType.years: - return notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - - case DateTimeIntervalType.months: - return (visibleRange!.minimum == current || current == previous!) - ? _firstLabelFormat() - : _normalDateFormat(current, previous); - - case DateTimeIntervalType.days: - return (visibleRange!.minimum == current || current == previous!) - ? _firstLabelFormat() - : _normalDateFormat(current, previous); - - case DateTimeIntervalType.hours: - return DateFormat.j(); - - case DateTimeIntervalType.minutes: - return DateFormat.Hm(); - - case DateTimeIntervalType.seconds: - return DateFormat.ms(); - - case DateTimeIntervalType.milliseconds: - return DateFormat('ss.SSS'); - - case DateTimeIntervalType.auto: - return DateFormat(); - } - } - - DateFormat _firstLabelFormat() { - late DateFormat format; - if (visibleIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (visibleIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (visibleIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - return format; - } - - // TODO(VijayakumarM): Optimize it. - DateFormat _normalDateFormat(num? current, int? previousLabel) { - final DateTime minimum = - DateTime.fromMillisecondsSinceEpoch(current!.toInt()); - final DateTime maximum = - DateTime.fromMillisecondsSinceEpoch(previousLabel!); - late DateFormat format; - final bool isIntervalDecimal = visibleInterval % 1 == 0; - if (visibleIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (visibleIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; - } - @override void calculateTickPositions( LabelPlacement placement, { diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart index 716f260d8..522aa6109 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/datetime_category_axis.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -472,22 +474,6 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { @override DoubleRange calculateActualRange() { - labels.clear(); - for (final AxisDependent dependent in dependents) { - if (dependent is CartesianSeriesRenderer) { - if (dependent.controller.isVisible) { - final List actualXValues = dependent.xRawValues; - final int actualXValuesLength = actualXValues.length; - for (int i = 0; i < actualXValuesLength; i++) { - final DateTime date = actualXValues[i] as DateTime; - if (!labels.contains(date.millisecondsSinceEpoch)) { - labels.add(date.millisecondsSinceEpoch); - } - } - } - } - } - if (minimum != null && maximum != null) { return DoubleRange( effectiveValue(minimum)!, effectiveValue(maximum, needMin: false)!); @@ -525,11 +511,13 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { _visibleIntervalType = DateTimeIntervalType.days; } - return interval ?? super.calculateNiceInterval(delta, availableSize); + return interval ?? + max(1, super.calculateNiceInterval(delta, availableSize)); } _visibleIntervalType = intervalType; - return interval ?? super.calculateNiceInterval(delta, availableSize); + return interval ?? + max(1, super.calculateNiceInterval(delta, availableSize)); } num _calculateIntervalAndType(num minimum, num maximum, Size availableSize) { @@ -663,7 +651,7 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { @override void generateVisibleLabels() { hasTrimmedAxisLabel = false; - if (visibleRange == null) { + if (visibleRange == null || visibleInterval == 0) { return; } @@ -681,8 +669,8 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { final num visibleMaximum = visibleRange!.maximum; num current = visibleMinimum.ceil(); num previous = current; - final DateFormat niceDateTimeFormat = - dateFormat ?? _niceDateFormat(current, previous.toInt()); + final DateFormat niceDateTimeFormat = dateFormat ?? + dateTimeCategoryAxisLabelFormat(this, current, previous.toInt()); while (current <= visibleMaximum) { if (current < visibleMinimum || !effectiveVisibleRange!.contains(current)) { @@ -750,73 +738,6 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { super.generateVisibleLabels(); } - DateFormat _niceDateFormat([num? current, int? previous]) { - final bool notDoubleInterval = - (interval != null && interval! % 1 == 0) || interval == null; - switch (visibleIntervalType) { - case DateTimeIntervalType.years: - return notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - - case DateTimeIntervalType.months: - return (visibleRange!.minimum == current || current == previous!) - ? _firstLabelFormat() - : _normalDateFormat(current, previous); - - case DateTimeIntervalType.days: - return (visibleRange!.minimum == current || current == previous!) - ? _firstLabelFormat() - : _normalDateFormat(current, previous); - - case DateTimeIntervalType.hours: - return DateFormat.j(); - - case DateTimeIntervalType.minutes: - return DateFormat.Hm(); - - case DateTimeIntervalType.seconds: - return DateFormat.ms(); - - case DateTimeIntervalType.milliseconds: - return DateFormat('ss.SSS'); - - case DateTimeIntervalType.auto: - return DateFormat(); - } - } - - DateFormat _firstLabelFormat() { - late DateFormat format; - if (visibleIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (visibleIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (visibleIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - return format; - } - - // TODO(VijayakumarM): Optimize it. - DateFormat _normalDateFormat(num? current, int? previousLabel) { - final DateTime minimum = - DateTime.fromMillisecondsSinceEpoch(current!.toInt()); - final DateTime maximum = - DateTime.fromMillisecondsSinceEpoch(previousLabel!); - late DateFormat format; - final bool isIntervalDecimal = visibleInterval % 1 == 0; - if (visibleIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (visibleIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; - } - @override void calculateTickPositions( LabelPlacement placement, { @@ -1064,6 +985,35 @@ class RenderDateTimeCategoryAxis extends RenderChartAxis { return index; } + // During sorting, always keep xValues as linear data. + void updateXValues() { + labels.clear(); + for (final AxisDependent dependent in dependents) { + if (dependent is CartesianSeriesRenderer && + dependent.controller.isVisible) { + final List actualXRawValues = dependent.xRawValues; + final int length = actualXRawValues.length; + const int minValue = 0; + int maxValue = 0; + if (length > 0) { + for (int i = 0; i < length; i++) { + final int x = + (actualXRawValues[i] as DateTime).millisecondsSinceEpoch; + if (!labels.contains(x)) { + labels.add(x); + } + final List xValues = dependent.xValues; + final int index = labels.indexOf(x); + xValues[i] = index; + maxValue = max(maxValue, index); + } + dependent.xMin = minValue; + dependent.xMax = maxValue; + } + } + } + } + @override void dispose() { _multilevelLabels.clear(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart index b2cffb4f9..fe06f0e82 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/logarithmic_axis.dart @@ -389,7 +389,7 @@ class RenderLogarithmicAxis extends RenderChartAxis { @override void addDependent(AxisDependent dependent, {bool isXAxis = true}) { super.addDependent(dependent, isXAxis: isXAxis); - if (isVertical && dependent.runtimeType.toString().contains('100')) { + if (isVertical && dependent is Stacking100SeriesMixin) { _dependentIsStacked = true; } } @@ -512,7 +512,7 @@ class RenderLogarithmicAxis extends RenderChartAxis { @override void generateVisibleLabels() { hasTrimmedAxisLabel = false; - if (visibleRange == null) { + if (visibleRange == null || visibleInterval == 0) { return; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart b/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart index c90a871e5..970cd8b41 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/axis/numeric_axis.dart @@ -383,7 +383,7 @@ class RenderNumericAxis extends RenderChartAxis { @override void addDependent(AxisDependent dependent, {bool isXAxis = true}) { super.addDependent(dependent, isXAxis: isXAxis); - if (isVertical && dependent.runtimeType.toString().contains('100')) { + if (isVertical && dependent is Stacking100SeriesMixin) { _dependentIsStacked = true; } } @@ -475,6 +475,9 @@ class RenderNumericAxis extends RenderChartAxis { @override void generateVisibleLabels() { hasTrimmedAxisLabel = false; + if (visibleRange == null || visibleInterval == 0) { + return; + } final double extent = labelsExtent ?? (maximumLabelWidth ?? double.maxFinite); final bool isRtl = textDirection == TextDirection.rtl; @@ -497,27 +500,8 @@ class RenderNumericAxis extends RenderChartAxis { currentValue = currentText.contains('e') ? currentValue : num.tryParse(currentValue.toStringAsFixed(digits))!; - if (piecesLength > 1) { - currentValue = num.parse(currentValue.toStringAsFixed(decimalPlaces)); - final String decimals = pieces[1]; - if (decimals == '0' || - decimals == '00' || - decimals == '000' || - decimals == '0000' || - decimals == '00000' || - currentValue % 1 == 0) { - currentValue = currentValue.round(); - } - } - String text = currentValue.toString(); - if (numberFormat != null) { - text = numberFormat!.format(currentValue); - } - - if (labelFormat != null && labelFormat != '') { - text = labelFormat!.replaceAll(RegExp('{value}'), text); - } + String text = numericAxisLabel(this, currentValue, decimalPlaces); if (_dependentIsStacked) { text = '$text%'; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/base.dart b/packages/syncfusion_flutter_charts/lib/src/charts/base.dart index 7e8b889e7..3b1057b37 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/base.dart @@ -11,6 +11,9 @@ import 'package:syncfusion_flutter_core/theme.dart'; import 'axis/axis.dart'; import 'axis/category_axis.dart'; +import 'behaviors/crosshair.dart'; +import 'behaviors/trackball.dart'; +import 'behaviors/zooming.dart'; import 'common/annotation.dart'; import 'common/callbacks.dart'; import 'common/core_legend.dart'; @@ -391,21 +394,17 @@ class RenderChartArea extends RenderBox _handlePointerMove(event); } else if (event is PointerHoverEvent) { _handlePointerHover(event); - } else if (event is PointerPanZoomUpdateEvent || - event is PointerScrollEvent) { - _handlePanZoomUpdate(event); } else if (event is PointerUpEvent) { _handlePointerUp(event); - } else if (event is PointerCancelEvent) { - _handlePointerCancel(event); + } + + if (_isBehaviorAreaHit(event.position)) { + _behaviorArea?.handleEvent(event, entry); } } bool _isCartesianAxesHit(Offset globalPosition) { if (_cartesianAxes != null) { - // final Offset localPosition = - // _cartesianAxes!.globalToLocal(globalPosition); - // return _cartesianAxes!.size.contains(localPosition); return true; } return false; @@ -421,8 +420,6 @@ class RenderChartArea extends RenderBox bool _isBehaviorAreaHit(Offset globalPosition) { if (_behaviorArea != null) { - // final Offset localPosition = _behaviorArea!.globalToLocal(globalPosition); - // return _behaviorArea!.size.contains(localPosition); return true; } return false; @@ -446,18 +443,12 @@ class RenderChartArea extends RenderBox } }); } - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePointerDown(details); - } } @protected void _handlePointerMove(PointerMoveEvent details) { onChartTouchInteractionMove?.call(ChartTouchInteractionArgs() ..position = globalToLocal(details.position)); - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePointerMove(details); - } } @protected @@ -476,9 +467,6 @@ class RenderChartArea extends RenderBox } }); } - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePointerHover(details); - } } @protected @@ -492,16 +480,6 @@ class RenderChartArea extends RenderBox } }); } - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePointerUp(details); - } - } - - @protected - void _handlePointerCancel(PointerCancelEvent details) { - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePointerCancel(details); - } } @protected @@ -603,6 +581,13 @@ class RenderChartArea extends RenderBox @protected void _handleScaleUpdate(ScaleUpdateDetails details) { + if (_isPlotAreaHit(details.focalPoint)) { + _plotArea?.visitChildren((RenderObject child) { + if (child is ChartSeriesRenderer) { + child.handleScaleUpdate(details); + } + }); + } if (_isBehaviorAreaHit(details.focalPoint)) { _isScaled = true; _behaviorArea?.handleScaleUpdate(details); @@ -617,13 +602,6 @@ class RenderChartArea extends RenderBox } } - @protected - void _handlePanZoomUpdate(PointerEvent details) { - if (_isBehaviorAreaHit(details.position)) { - _behaviorArea?.handlePanZoomUpdate(details); - } - } - @override void paint(PaintingContext context, Offset offset) { defaultPaint(context, offset); @@ -1291,6 +1269,7 @@ class RenderCartesianChartPlotArea extends RenderChartPlotArea { late num _primaryAxisAdjacentDataPointsMinDiff; RenderCartesianAxes? _cartesianAxes; Map>? sbsDetails; + bool isLegendToggled = false; bool get isTransposed => _isTransposed; bool _isTransposed = false; @@ -1611,6 +1590,7 @@ class RenderCartesianChartPlotArea extends RenderChartPlotArea { } }); super.performUpdate(); + markNeedsLayout(); } @override @@ -1620,6 +1600,19 @@ class RenderCartesianChartPlotArea extends RenderChartPlotArea { _cartesianAxes!.plotAreaBounds = (parentData! as BoxParentData).offset & size; } + + // Once all cartesian series layouts are completed, use this method to + // handle the collisions of data labels across multiple series. + visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none && + child.dataLabelContainer != null) { + child.dataLabelContainer!.handleMultiSeriesDataLabelCollisions(); + } + }); } bool _hasDataLabel() { @@ -2289,6 +2282,8 @@ class IndicatorStack extends StatefulWidget { required this.isTransposed, required this.onLegendTapped, required this.onLegendItemRender, + this.trackballBehavior, + this.textDirection, }); final TickerProvider vsync; @@ -2296,6 +2291,8 @@ class IndicatorStack extends StatefulWidget { final bool isTransposed; final ChartLegendTapCallback? onLegendTapped; final ChartLegendRenderCallback? onLegendItemRender; + final TrackballBehavior? trackballBehavior; + final TextDirection? textDirection; @override State createState() => _IndicatorStackState(); @@ -2425,6 +2422,8 @@ class _IndicatorStackState extends State { return IndicatorArea( indicators: widget.indicators, + trackballBehavior: widget.trackballBehavior, + textDirection: widget.textDirection, children: children, ); } @@ -2434,21 +2433,31 @@ class IndicatorArea extends MultiChildRenderObjectWidget { const IndicatorArea({ super.key, required this.indicators, + this.trackballBehavior, + this.textDirection, super.children, }); final List indicators; + final TrackballBehavior? trackballBehavior; + final TextDirection? textDirection; @override RenderObject createRenderObject(BuildContext context) { - return RenderIndicatorArea()..indicators = indicators; + return RenderIndicatorArea() + ..indicators = indicators + ..trackballBehavior = trackballBehavior + ..textDirection = textDirection; } @override void updateRenderObject( BuildContext context, RenderIndicatorArea renderObject) { super.updateRenderObject(context, renderObject); - renderObject.indicators = indicators; + renderObject + ..indicators = indicators + ..trackballBehavior = trackballBehavior + ..textDirection = textDirection; } } @@ -2458,6 +2467,8 @@ class RenderIndicatorArea extends RenderBox RenderBoxContainerDefaultsMixin, ChartAreaUpdateMixin { RenderCartesianChartArea? chartArea; + TrackballBehavior? trackballBehavior; + late TextDirection? textDirection; late List indicators; final Map series = {}; @@ -2669,24 +2680,22 @@ class RenderAnnotationArea extends RenderStack with ChartAreaUpdateMixin { if (annotation.region == AnnotationRegion.plotArea) { final RenderChartAxis? xAxis = _xAxis(annotation); if (xAxis != null) { - width = isTransposed ? xAxis.size.height : xAxis.size.width; + width = _plotAreaBounds.size.width; } final RenderChartAxis? yAxis = _yAxis(annotation); if (yAxis != null) { - height = isTransposed ? yAxis.size.width : yAxis.size.height; + height = _plotAreaBounds.size.height; } } else { - if (isTransposed) { - width = size.height; - height = size.width; - } else { - width = size.width; - height = size.height; - } + width = size.width; + height = size.height; } - return Offset(width * xFactor, height * yFactor); + return Offset(width * xFactor, height * yFactor) + + (annotation.region == AnnotationRegion.plotArea + ? _plotAreaOffset + : Offset.zero); } RenderChartAxis? _xAxis(CartesianChartAnnotation annotation) { @@ -2735,7 +2744,7 @@ class RenderAnnotationArea extends RenderStack with ChartAreaUpdateMixin { double _horizontalAlignment( ChartAlignment horizontalAlignment, double xPosition, Size childSize) { - final double size = isTransposed ? childSize.height : childSize.width; + final double size = childSize.width; switch (horizontalAlignment) { case ChartAlignment.near: return xPosition; @@ -2748,7 +2757,7 @@ class RenderAnnotationArea extends RenderStack with ChartAreaUpdateMixin { double _verticalAlignment( ChartAlignment verticalAlignment, double yPosition, Size childSize) { - final double size = isTransposed ? childSize.width : childSize.height; + final double size = childSize.height; switch (verticalAlignment) { case ChartAlignment.near: return yPosition; @@ -3109,6 +3118,65 @@ class ChartBehavior { _parentBox = value; } } + + /// To customize the necessary pointer events in behaviors. + /// (e.g., CrosshairBehavior, TrackballBehavior, ZoomingBehavior). + @protected + void handleEvent(PointerEvent event, BoxHitTestEntry entry) {} + + /// Called when a long press gesture by a primary button has been + /// recognized in behavior. + @protected + void handleLongPressStart(LongPressStartDetails details) {} + + /// Called when moving after the long press gesture by a primary button + /// is recognized in behavior. + @protected + void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {} + + /// Called when the pointer stops contacting the screen after a long-press by + /// a primary button in behavior. + @protected + void handleLongPressEnd(LongPressEndDetails details) {} + + /// Called when the pointer tap has contacted the screen in behavior. + @protected + void handleTapDown(TapDownDetails details) {} + + /// Called when pointer has stopped contacting screen in behavior. + @protected + void handleTapUp(TapUpDetails details) {} + + /// Called when pointer tap has contacted the screen double time. + @protected + void handleDoubleTap(Offset position) {} + + /// Called when the pointers in contact with the screen + /// and initial scale of 1.0. + @protected + void handleScaleStart(ScaleStartDetails details) {} + + /// Called when the pointers in contact with the screen have indicated + /// a new scale. + @protected + void handleScaleUpdate(ScaleUpdateDetails details) {} + + /// Called when the pointers are no longer in contact with the screen. + @protected + void handleScaleEnd(ScaleEndDetails details) {} + + /// Called when a pointer or mouse enter on the screen. + @protected + void handlePointerEnter(PointerEnterEvent details) {} + + /// Called when a pointer or mouse exit on the screen. + @protected + void handlePointerExit(PointerExitEvent details) {} + + /// Called to customize each behaviors with given context at the given offset. + @protected + void onPaint(PaintingContext context, Offset offset, + SfChartThemeData chartThemeData, ThemeData themeData) {} } typedef SelectionCallback = void Function(int seriesIndex, int pointIndex); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart index cda05650f..b83b09152 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/cartesian_chart.dart @@ -8,8 +8,12 @@ import 'package:syncfusion_flutter_core/theme.dart'; import 'axis/axis.dart'; import 'axis/numeric_axis.dart'; import 'base.dart'; +import 'behaviors/crosshair.dart'; +import 'behaviors/trackball.dart'; +import 'behaviors/zooming.dart'; import 'common/annotation.dart'; import 'common/callbacks.dart'; +import 'common/chart_point.dart'; import 'common/core_legend.dart' as core; import 'common/core_tooltip.dart'; import 'common/legend.dart'; @@ -17,12 +21,8 @@ import 'common/title.dart'; import 'indicators/technical_indicator.dart'; import 'interactions/behavior.dart'; import 'interactions/tooltip.dart'; -import 'interactions/trackball.dart'; -import 'series/box_and_whisker_series.dart'; -import 'series/candle_series.dart'; import 'series/chart_series.dart'; -import 'series/hilo_open_close_series.dart'; -import 'series/hilo_series.dart'; +import 'theme.dart'; import 'utils/constants.dart'; import 'utils/enum.dart'; import 'utils/helper.dart'; @@ -139,18 +139,7 @@ class SfCartesianChart extends StatefulWidget { this.annotations, this.loadMoreIndicatorBuilder, this.onPlotAreaSwipe, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], + this.palette, this.primaryXAxis = const NumericAxis(), this.primaryYAxis = const NumericAxis(), this.margin = const EdgeInsets.all(10), @@ -1015,7 +1004,7 @@ class SfCartesianChart extends StatefulWidget { /// ); /// } /// ``` - final List palette; + final List? palette; /// Technical indicators for charts. final List indicators; @@ -1190,57 +1179,126 @@ class SfCartesianChartState extends State SfLocalizations? _localizations; Widget? _trackballBuilder; - SfChartThemeData _updateThemeData(BuildContext context) { + SfChartThemeData _updateThemeData( + BuildContext context, SfChartThemeData effectiveChartThemeData) { SfChartThemeData chartThemeData = SfChartTheme.of(context); - _themeData = Theme.of(context); chartThemeData = chartThemeData.copyWith( - backgroundColor: widget.backgroundColor ?? chartThemeData.backgroundColor, - titleBackgroundColor: - widget.title.backgroundColor ?? chartThemeData.titleBackgroundColor, + axisLineColor: + chartThemeData.axisLineColor ?? effectiveChartThemeData.axisLineColor, + axisLabelColor: chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor, + axisTitleColor: chartThemeData.axisTitleColor ?? + effectiveChartThemeData.axisTitleColor, + titleTextColor: chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor, + crosshairBackgroundColor: chartThemeData.crosshairBackgroundColor ?? + effectiveChartThemeData.crosshairBackgroundColor, + crosshairLabelColor: chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor, + legendTextColor: chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor, + legendTitleColor: chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor, + majorGridLineColor: chartThemeData.majorGridLineColor ?? + effectiveChartThemeData.majorGridLineColor, + minorGridLineColor: chartThemeData.minorGridLineColor ?? + effectiveChartThemeData.minorGridLineColor, + majorTickLineColor: chartThemeData.majorTickLineColor ?? + effectiveChartThemeData.majorTickLineColor, + minorTickLineColor: chartThemeData.minorTickLineColor ?? + effectiveChartThemeData.minorTickLineColor, + selectionRectColor: chartThemeData.selectionRectColor ?? + effectiveChartThemeData.selectionRectColor, + selectionRectBorderColor: chartThemeData.selectionRectBorderColor ?? + effectiveChartThemeData.selectionRectBorderColor, + selectionTooltipConnectorLineColor: + chartThemeData.selectionTooltipConnectorLineColor ?? + effectiveChartThemeData.selectionTooltipConnectorLineColor, + waterfallConnectorLineColor: chartThemeData.waterfallConnectorLineColor ?? + effectiveChartThemeData.waterfallConnectorLineColor, + tooltipLabelColor: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + tooltipSeparatorColor: chartThemeData.tooltipSeparatorColor ?? + effectiveChartThemeData.tooltipSeparatorColor, + backgroundColor: widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, plotAreaBackgroundColor: widget.plotAreaBackgroundColor ?? - chartThemeData.plotAreaBackgroundColor, - plotAreaBorderColor: - widget.plotAreaBorderColor ?? chartThemeData.plotAreaBorderColor, - legendBackgroundColor: - widget.legend.backgroundColor ?? chartThemeData.legendBackgroundColor, + chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + plotAreaBorderColor: widget.plotAreaBorderColor ?? + chartThemeData.plotAreaBorderColor ?? + effectiveChartThemeData.plotAreaBorderColor, + legendBackgroundColor: widget.legend.backgroundColor ?? + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, crosshairLineColor: widget.crosshairBehavior?.lineColor ?? - chartThemeData.crosshairLineColor, - titleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.titleTextColor, fontSize: 15) + chartThemeData.crosshairLineColor ?? + effectiveChartThemeData.crosshairLineColor, + tooltipColor: widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor) .merge(chartThemeData.titleTextStyle) .merge(widget.title.textStyle), - axisTitleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.axisTitleColor, fontSize: 15) + axisTitleTextStyle: effectiveChartThemeData.axisTitleTextStyle! + .copyWith( + color: chartThemeData.axisTitleColor ?? + effectiveChartThemeData.axisTitleColor) .merge(chartThemeData.axisTitleTextStyle), - axisLabelTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.axisLabelColor) + axisLabelTextStyle: effectiveChartThemeData.axisLabelTextStyle! + .copyWith( + color: chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor) .merge(chartThemeData.axisLabelTextStyle), - axisMultiLevelLabelTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.axisLabelColor) + axisMultiLevelLabelTextStyle: effectiveChartThemeData + .axisMultiLevelLabelTextStyle! + .copyWith( + color: chartThemeData.axisLabelColor ?? + effectiveChartThemeData.axisLabelColor) .merge(chartThemeData.axisMultiLevelLabelTextStyle), - plotBandLabelTextStyle: _themeData.textTheme.bodySmall! + plotBandLabelTextStyle: effectiveChartThemeData.plotBandLabelTextStyle! .merge(chartThemeData.plotBandLabelTextStyle), - legendTitleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.legendTitleColor) + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor) .merge(chartThemeData.legendTitleTextStyle) .merge(widget.legend.title?.textStyle), - legendTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.legendTextColor, fontSize: 13) + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor) .merge(chartThemeData.legendTextStyle) .merge(widget.legend.textStyle), - tooltipTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.tooltipLabelColor) + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor) .merge(chartThemeData.tooltipTextStyle) .merge(widget.tooltipBehavior?.textStyle), - trackballTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.crosshairLabelColor) + trackballTextStyle: effectiveChartThemeData.trackballTextStyle! + .copyWith( + color: chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor) .merge(chartThemeData.trackballTextStyle) .merge(widget.trackballBehavior?.tooltipSettings.textStyle), - crosshairTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.crosshairLabelColor) + crosshairTextStyle: effectiveChartThemeData.crosshairTextStyle! + .copyWith( + color: chartThemeData.crosshairLabelColor ?? + effectiveChartThemeData.crosshairLabelColor) .merge(chartThemeData.crosshairTextStyle), - selectionZoomingTooltipTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: chartThemeData.tooltipLabelColor) + selectionZoomingTooltipTextStyle: effectiveChartThemeData + .selectionZoomingTooltipTextStyle! + .copyWith( + color: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor) .merge(chartThemeData.selectionZoomingTooltipTextStyle), ); return chartThemeData; @@ -1271,52 +1329,29 @@ class SfCartesianChartState extends State } void _buildTrackballWidget(List details) { - _trackballBuilder = details.isEmpty || - widget.trackballBehavior!.builder == null - ? const SizedBox( - height: 0, - ) - : Stack( - children: List.generate( - details.length, - (int index) => TrackballRenderObject( - chartPointInfo: widget.trackballBehavior!.chartPointInfo, - template: widget.trackballBehavior!.builder! - .call(context, details[index]), - trackballBehavior: widget.trackballBehavior!, - xPos: (details[index].series - is RangeSeriesRendererBase) || - (details[index].series - is HiloOpenCloseSeriesRenderer || - details[index].series - is HiloSeriesRenderer) || - details[index].series is CandleSeriesRenderer - ? widget.trackballBehavior!.chartPointInfo[index] - .highXPosition! - : details[index].series is BoxAndWhiskerSeriesRenderer - ? widget.trackballBehavior!.chartPointInfo[index] - .maxXPosition! - : widget.trackballBehavior!.chartPointInfo[index] - .xPosition!, - yPos: (details[index].series - is RangeSeriesRendererBase) || - (details[index].series - is HiloOpenCloseSeriesRenderer || - details[index].series - is HiloSeriesRenderer) || - details[index].series is CandleSeriesRenderer - ? widget.trackballBehavior!.chartPointInfo[index] - .highYPosition! - : details[index].series is BoxAndWhiskerSeriesRenderer - ? widget.trackballBehavior!.chartPointInfo[index] - .maxYPosition! - : widget.trackballBehavior!.chartPointInfo[index] - .yPosition!, - index: index, - child: widget.trackballBehavior!.builder! - .call(context, details[index]), - )).toList(), + final TrackballBehavior trackballBehavior = widget.trackballBehavior!; + if (details.isEmpty || trackballBehavior.builder == null) { + _trackballBuilder = const SizedBox(width: 0, height: 0); + } else if (details.isNotEmpty && trackballBehavior.builder != null) { + _trackballBuilder = Stack( + children: List.generate(details.length, (int index) { + final List chartPointInfo = + trackballBehavior.chartPointInfo; + final ChartPointInfo info = chartPointInfo[index]; + final Widget builder = + trackballBehavior.builder!.call(context, details[index]); + return TrackballBuilderRenderObjectWidget( + index: index, + xPos: info.xPosition!, + yPos: info.yPosition!, + builder: builder, + chartPointInfo: chartPointInfo, + trackballBehavior: trackballBehavior, + child: builder, ); + }).toList(), + ); + } final RenderObjectElement? trackballBuilderElement = _trackballBuilderKey.currentContext as RenderObjectElement?; if (trackballBuilderElement != null && @@ -1370,8 +1405,11 @@ class SfCartesianChartState extends State @override Widget build(BuildContext context) { - _chartThemeData = _updateThemeData(context); - final ThemeData themeData = Theme.of(context); + _themeData = Theme.of(context); + final SfChartThemeData effectiveChartThemeData = _themeData.useMaterial3 + ? SfChartThemeDataM3(context) + : SfChartThemeDataM2(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); bool isTransposed = widget.isTransposed; if (widget.series.isNotEmpty && widget.series[0].transposed()) { isTransposed = !isTransposed; @@ -1443,7 +1481,10 @@ class SfCartesianChartState extends State onDataLabelTapped: widget.onDataLabelTapped, onMarkerRender: widget.onMarkerRender, onTooltipRender: widget.onTooltipRender, - palette: widget.palette, + palette: widget.palette ?? + (_themeData.useMaterial3 + ? (effectiveChartThemeData as SfChartThemeDataM3).palette + : (effectiveChartThemeData as SfChartThemeDataM2).palette), selectionMode: widget.selectionType, selectionGesture: widget.selectionGesture, enableMultiSelection: widget.enableMultiSelection, @@ -1453,7 +1494,7 @@ class SfCartesianChartState extends State zoomPanBehavior: widget.zoomPanBehavior, onSelectionChanged: widget.onSelectionChanged, chartThemeData: _chartThemeData, - themeData: themeData, + themeData: _themeData, children: widget.series, ), CartesianAxes( @@ -1477,6 +1518,8 @@ class SfCartesianChartState extends State indicators: widget.indicators, onLegendItemRender: widget.onLegendItemRender, onLegendTapped: widget.onLegendTapped, + trackballBehavior: widget.trackballBehavior, + textDirection: Directionality.of(context), ), if (widget.annotations != null && widget.annotations!.isNotEmpty && @@ -1504,6 +1547,7 @@ class SfCartesianChartState extends State textDirection: Directionality.of(context), trackballBehavior: widget.trackballBehavior, chartThemeData: _chartThemeData, + themeData: _themeData, trackballBuilder: _buildTrackballWidget, onTrackballPositionChanging: widget.onTrackballPositionChanging, onCrosshairPositionChanging: widget.onCrosshairPositionChanging, @@ -1512,11 +1556,15 @@ class SfCartesianChartState extends State if (widget.trackballBehavior != null && widget.trackballBehavior!.enable && widget.trackballBehavior!.builder != null) - LayoutBuilder( - key: _trackballBuilderKey, - builder: (BuildContext context, BoxConstraints constraints) { - return _trackballBuilder ?? const SizedBox(height: 0); - }, + TrackballBuilderOpacityWidget( + opacity: 1.0, + child: LayoutBuilder( + key: _trackballBuilderKey, + builder: + (BuildContext context, BoxConstraints constraints) { + return _trackballBuilder ?? const SizedBox(height: 0); + }, + ), ), if (widget.loadMoreIndicatorBuilder != null || widget.onPlotAreaSwipe != null) @@ -1534,8 +1582,8 @@ class SfCartesianChartState extends State opacity: widget.tooltipBehavior!.opacity, borderColor: widget.tooltipBehavior!.borderColor, borderWidth: widget.tooltipBehavior!.borderWidth, - color: widget.tooltipBehavior!.color ?? - _chartThemeData.tooltipColor, + color: (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, showDuration: widget.tooltipBehavior!.duration.toInt(), shadowColor: widget.tooltipBehavior!.shadowColor, elevation: widget.tooltipBehavior!.elevation, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart index 19d6cb39a..47572b5c0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/circular_chart.dart @@ -15,6 +15,7 @@ import 'common/title.dart'; import 'interactions/behavior.dart'; import 'interactions/tooltip.dart'; import 'series/chart_series.dart'; +import 'theme.dart'; import 'utils/enum.dart'; import 'utils/helper.dart'; import 'utils/typedef.dart'; @@ -71,18 +72,7 @@ class SfCircularChart extends StatefulWidget { this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, this.onCreateShader, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], + this.palette, this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), this.series = const [], this.title = const ChartTitle(), @@ -519,7 +509,7 @@ class SfCircularChart extends StatefulWidget { /// ); /// } /// ``` - final List palette; + final List? palette; /// Gesture for activating the selection. /// @@ -598,34 +588,50 @@ class SfCircularChartState extends State SfLocalizations? _localizations; List? _annotations; - SfChartThemeData _updateThemeData(BuildContext context) { - _chartThemeData = SfChartTheme.of(context); - _themeData = Theme.of(context); - _chartThemeData = _chartThemeData.copyWith( - backgroundColor: - widget.backgroundColor ?? _chartThemeData.backgroundColor, - titleBackgroundColor: - widget.title.backgroundColor ?? _chartThemeData.titleBackgroundColor, + SfChartThemeData _updateThemeData( + BuildContext context, SfChartThemeData effectiveChartThemeData) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, legendBackgroundColor: widget.legend.backgroundColor ?? - _chartThemeData.legendBackgroundColor, - titleTextStyle: _themeData.textTheme.bodyMedium! - .copyWith(color: _chartThemeData.titleTextColor, fontSize: 15) - .merge(_chartThemeData.titleTextStyle) + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor) + .merge(chartThemeData.titleTextStyle) .merge(widget.title.textStyle), - legendTitleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTitleColor) - .merge(_chartThemeData.legendTitleTextStyle) + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor) + .merge(chartThemeData.legendTitleTextStyle) .merge(widget.legend.title?.textStyle), - legendTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTextColor, fontSize: 13) - .merge(_chartThemeData.legendTextStyle) + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor) + .merge(chartThemeData.legendTextStyle) .merge(widget.legend.textStyle), - tooltipTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.tooltipLabelColor) - .merge(_chartThemeData.tooltipTextStyle) + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor) + .merge(chartThemeData.tooltipTextStyle) .merge(widget.tooltipBehavior?.textStyle), ); - return _chartThemeData; + return chartThemeData; } Widget _buildLegendItem(BuildContext context, int index) { @@ -752,8 +758,11 @@ class SfCircularChartState extends State /// in [SfCircularChart]. @override Widget build(BuildContext context) { - _chartThemeData = _updateThemeData(context); - final ThemeData themeData = Theme.of(context); + _themeData = Theme.of(context); + final SfChartThemeData effectiveChartThemeData = _themeData.useMaterial3 + ? SfChartThemeDataM3(context) + : SfChartThemeDataM2(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); final core.LegendPosition legendPosition = effectiveLegendPosition(widget.legend); final Axis orientation = @@ -805,9 +814,12 @@ class SfCircularChartState extends State vsync: this, localizations: _localizations, legendKey: _legendKey, - palette: widget.palette, + palette: widget.palette ?? + (_themeData.useMaterial3 + ? (effectiveChartThemeData as SfChartThemeDataM3).palette + : (effectiveChartThemeData as SfChartThemeDataM2).palette), chartThemeData: _chartThemeData, - themeData: themeData, + themeData: _themeData, backgroundColor: widget.backgroundColor, borderWidth: widget.borderWidth, legend: widget.legend, @@ -838,6 +850,7 @@ class SfCircularChartState extends State BehaviorArea( tooltipKey: _tooltipKey, chartThemeData: _chartThemeData, + themeData: _themeData, tooltipBehavior: widget.tooltipBehavior, onTooltipRender: widget.onTooltipRender, children: [ @@ -849,8 +862,8 @@ class SfCircularChartState extends State opacity: widget.tooltipBehavior!.opacity, borderColor: widget.tooltipBehavior!.borderColor, borderWidth: widget.tooltipBehavior!.borderWidth, - color: widget.tooltipBehavior!.color ?? - _chartThemeData.tooltipColor, + color: (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, showDuration: widget.tooltipBehavior!.duration.toInt(), shadowColor: widget.tooltipBehavior!.shadowColor, elevation: widget.tooltipBehavior!.elevation, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart index 83b5eaba6..f38089f68 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/chart_point.dart @@ -183,6 +183,9 @@ class ChartPoint with Diagnosticable { /// Color of the point. Color? color; + /// To get the visibility of chart point. + bool isVisible = true; + dynamic operator [](ChartDataPointType pointType) { return y!; } @@ -229,9 +232,6 @@ class CircularChartPoint extends ChartPoint { /// Outer radius of chart point. num? outerRadius; - /// To set the visibility of chart point. - bool isVisible = true; - /// To set the explode value of chart point. bool isExplode = false; @@ -370,81 +370,110 @@ class PointInfo { /// Represents the class of chart point info class ChartPointInfo { - /// Marker x position + ChartPointInfo({ + this.chartPoint, + this.dataPointIndex, + this.label, + this.series, + this.seriesName, + this.seriesIndex, + this.color, + this.header, + this.xPosition, + this.yPosition, + this.highXPosition, + this.highYPosition, + this.openXPosition, + this.openYPosition, + this.lowYPosition, + this.closeXPosition, + this.closeYPosition, + this.minYPosition, + this.maxXPosition, + this.maxYPosition, + this.lowerXPosition, + this.lowerYPosition, + this.upperXPosition, + this.upperYPosition, + this.markerXPos, + this.markerYPos, + }); + + /// Marker x position. double? markerXPos; - /// Marker y position + /// Marker y position. double? markerYPos; - /// label for trackball and cross hair + /// label for trackball and cross hair. String? label; - /// Data point index + /// Data point index. int? dataPointIndex; - /// Instance of chart series + /// Instance of chart series. dynamic series; - /// Chart data point - CartesianChartPoint? chartDataPoint; + /// Cartesian chart point. + CartesianChartPoint? chartPoint; - /// X position of the label + /// X position of the label. double? xPosition; - /// Y position of the label + /// Y position of the label. double? yPosition; - /// Color of the segment + /// Color of the segment. Color? color; - /// header text + /// header text. String? header; - /// Low Y position of financial series + /// Low Y position of financial series. double? lowYPosition; - /// High X position of financial series + /// High X position of financial series. double? highXPosition; - /// High Y position of financial series + /// High Y position of financial series. double? highYPosition; - /// Open y position of financial series + /// Open y position of financial series. double? openYPosition; - /// close y position of financial series + /// close y position of financial series. double? closeYPosition; - /// open x position of financial series + /// open x position of financial series. double? openXPosition; - /// close x position of financial series + /// close x position of financial series. double? closeXPosition; - /// Minimum Y position of box plot series + /// Minimum Y position of box plot series. double? minYPosition; - /// Maximum Y position of box plot series + /// Maximum Y position of box plot series. double? maxYPosition; - /// Lower y position of box plot series + /// Lower y position of box plot series. double? lowerXPosition; - /// Upper y position of box plot series + /// Upper y position of box plot series. double? upperXPosition; - /// Lower x position of box plot series + /// Lower x position of box plot series. double? lowerYPosition; - /// Upper x position of box plot series + /// Upper x position of box plot series. double? upperYPosition; - /// Maximum x position for box plot series + /// Maximum x position for box plot series. double? maxXPosition; - /// series index value - late int seriesIndex; + /// series index value. + int? seriesIndex; - /// series name value + /// series name value. String? seriesName; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart index 4404cd857..5350be3d2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label.dart @@ -222,8 +222,13 @@ class _CircularDataLabelContainerState return; } + final bool hasSortedIndexes = renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + for (int i = 0; i < renderer!.dataCount; i++) { - _obtainLabel(i, actualXValues, yLength, positions, posAdj, callback, add); + _obtainLabel(i, actualXValues, yLength, positions, posAdj, callback, add, + hasSortedIndexes); } } @@ -240,7 +245,9 @@ class _CircularDataLabelContainerState int posAdj, _ChartDataLabelWidgetBuilder callback, Function(CircularChartDataLabelPositioned) add, + bool hasSortedIndexes, ) { + final int pointIndex = hasSortedIndexes ? sortedIndexes![index] : index; final num x = xValues![index]; final CircularChartPoint point = CircularChartPoint(x: rawXValues[index] as D?); @@ -264,10 +271,10 @@ class _CircularDataLabelContainerState position: position, point: point, child: callback( - widget.dataSource[index], + widget.dataSource[pointIndex], point, widget.series, - index, + pointIndex, renderer!.index, position, ), @@ -364,6 +371,7 @@ class RenderCircularDataLabelStack extends RenderChartElementStack { late CircularSeriesRenderer? series; late LinkedList? labels; late DataLabelSettings settings; + bool hasTrimmedDataLabel = false; List widgets = []; @@ -378,10 +386,14 @@ class RenderCircularDataLabelStack extends RenderChartElementStack { @override bool hitTestSelf(Offset position) { - return true; + return hasTrimmedDataLabel || series?.parent?.onDataLabelTapped != null; } int _findSelectedDataLabelIndex(Offset localPosition) { + if (series?.parent?.onDataLabelTapped == null && !hasTrimmedDataLabel) { + return -1; + } + if (childCount > 0) { RenderBox? child = lastChild; while (child != null) { @@ -427,13 +439,15 @@ class RenderCircularDataLabelStack extends RenderChartElementStack { settings, selectedIndex, )); - } else { + } else if (hasTrimmedDataLabel) { final int selectedIndex = _findSelectedDataLabelIndex(localPosition); if (selectedIndex == -1) { return; } final CircularChartPoint point = labels!.elementAt(selectedIndex).point!; - if (point.trimmedText != null && point.text != point.trimmedText) { + if (point.isVisible && + point.trimmedText != null && + point.text != point.trimmedText) { _showTooltipForTrimmedDataLabel(point, selectedIndex); } } @@ -441,13 +455,17 @@ class RenderCircularDataLabelStack extends RenderChartElementStack { @override void handlePointerHover(Offset localPosition) { - final int selectedIndex = _findSelectedDataLabelIndex(localPosition); - if (selectedIndex == -1) { - return; - } - final CircularChartPoint point = labels!.elementAt(selectedIndex).point!; - if (point.trimmedText != null && point.text != point.trimmedText) { - _showTooltipForTrimmedDataLabel(point, selectedIndex); + if (hasTrimmedDataLabel) { + final int selectedIndex = _findSelectedDataLabelIndex(localPosition); + if (selectedIndex == -1) { + return; + } + final CircularChartPoint point = labels!.elementAt(selectedIndex).point!; + if (point.isVisible && + point.trimmedText != null && + point.text != point.trimmedText) { + _showTooltipForTrimmedDataLabel(point, selectedIndex); + } } } @@ -527,18 +545,18 @@ class RenderCircularDataLabelStack extends RenderChartElementStack { currentLabel.size = measureText(details.text, details.textStyle); currentLabel.offset += series!.dataLabelPosition(currentLabelData, currentLabel.size); + hasTrimmedDataLabel = currentLabel.point!.trimmedText != null; if (currentLabel.point!.text != details.text) { details.text = currentLabel.point!.text!; currentLabel.size = measureText(details.text, details.textStyle); } - - // TODO(Lavanya): Need to handle the offset value for the - // shift data label. } if (series!.dataLabelSettings.labelIntersectAction == LabelIntersectAction.shift) { shiftCircularDataLabels(series!, labels!); + hasTrimmedDataLabel = + labels!.every((element) => element.point!.trimmedText != null); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart index 36efb385c..1a580c6ab 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/circular_data_label_helper.dart @@ -193,15 +193,14 @@ bool _isInsideSegment( Color findThemeColor(CircularSeriesRenderer seriesRenderer, CircularChartPoint point, DataLabelSettings dataLabelSettings) { // TODO(Lavanya): Recheck here. + final Color dataLabelBackgroundColor = + seriesRenderer.parent!.themeData!.colorScheme.surface; if (dataLabelSettings.color != null) { return dataLabelSettings.color!; } else { return (dataLabelSettings.useSeriesColor ? point.fill - : (seriesRenderer.parent!.backgroundColor ?? - (seriesRenderer.chartThemeData?.brightness == Brightness.light - ? Colors.white - : Colors.black))); + : (seriesRenderer.parent!.backgroundColor ?? dataLabelBackgroundColor)); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart index bd78abc1d..573069d2d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_legend.dart @@ -189,7 +189,7 @@ class LegendItem { this.degree, this.endAngle, this.startAngle, - }) : assert(iconColor != null || shader != null || imageProvider != null); + }); /// Specifies the text of the legend. final String text; @@ -1657,9 +1657,7 @@ class _IconTextState extends State<_IconText> Widget current = CustomPaint( size: widget.iconSize, painter: _LegendIconShape( - color: widget.iconOpacity != null - ? details.color!.withOpacity(widget.iconOpacity) - : details.color, + color: details.color!.withOpacity(widget.iconOpacity), iconType: details.iconType, iconBorderColor: details.iconBorderColor, iconBorderWidth: details.iconBorderWidth, @@ -1759,11 +1757,12 @@ class _IconTextState extends State<_IconText> @override void didUpdateWidget(_IconText oldWidget) { - if (widget.details.iconColor != oldWidget.details.iconColor) { + if (widget.details.iconColor != oldWidget.details.iconColor || + widget.details.shader != oldWidget.details.shader) { _iconColorTween.begin = widget.details.shader == null && widget.details.imageProvider == null ? widget.details.iconColor - : null; + : Colors.transparent; } if (widget.toggledColor != null && @@ -1806,12 +1805,9 @@ class _IconTextState extends State<_IconText> Widget current; if (widget.itemBuilder != null) { current = widget.itemBuilder!.call(context, widget.index); - if (widget.toggleEnabled != null) { - final Color? color = - _shaderMaskColorTween.evaluate(_toggleAnimation); - if (color != null) { - current = _buildShaderMask(color, current); - } + final Color? color = _shaderMaskColorTween.evaluate(_toggleAnimation); + if (color != null) { + current = _buildShaderMask(color, current); } } else { final Color? effectiveIconColor = diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart index a6d3fb82f..4e694fd90 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/core_tooltip.dart @@ -56,6 +56,31 @@ class TooltipInfo { } } +class TooltipOpacity extends Opacity { + const TooltipOpacity({ + super.key, + required super.opacity, + super.alwaysIncludeSemantics = false, + super.child, + }); + + @override + RenderOpacity createRenderObject(BuildContext context) { + return TooltipOpacityRenderBox( + opacity: opacity, + alwaysIncludeSemantics: alwaysIncludeSemantics, + ); + } +} + +class TooltipOpacityRenderBox extends RenderOpacity { + TooltipOpacityRenderBox({ + super.opacity = 1.0, + super.alwaysIncludeSemantics = false, + super.child, + }); +} + class CoreTooltip extends StatefulWidget { const CoreTooltip({ super.key, @@ -202,7 +227,7 @@ class CoreTooltipState extends State chartThemeData.platform == TargetPlatform.macOS || chartThemeData.platform == TargetPlatform.windows || chartThemeData.platform == TargetPlatform.linux; - return Opacity( + return TooltipOpacity( opacity: widget.opacity, child: LayoutBuilder( key: _tooltipKey, @@ -498,8 +523,26 @@ class _CoreTooltipRenderBox extends RenderProxyBox { super.detach(); } + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (_state._animation.value == 1.0 && + child != null && + child!.parentData != null) { + final BoxParentData childParentData = child!.parentData! as BoxParentData; + return result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child!.hitTest(result, position: transformed); + }, + ); + } + return false; + } + @override void performLayout() { + // Hide the tooltip while resizing. if (!hasSize || size != constraints.biggest) { _nosePosition = null; _primaryPosition = null; @@ -534,10 +577,18 @@ class _CoreTooltipRenderBox extends RenderProxyBox { innerPadding: _innerPadding, ); + final int multiplier = (_effectivePreferTooltipOnTop! ? 1 : -1); + final double halfTooltipHeight = child!.size.height / 2; + final Offset pathShift = Offset( + _nosePosition!.dx, + _nosePosition!.dy - + (_triangleHeight * multiplier + halfTooltipHeight * multiplier), + ); + _path = _path.shift(pathShift); + final Rect pathBounds = _path.getBounds(); final Offset pathCenter = pathBounds.center; - final double triangleHeight = - _triangleHeight * (_effectivePreferTooltipOnTop! ? 1 : -1); + final double triangleHeight = _triangleHeight * multiplier; final Offset childPosition = Offset( pathCenter.dx - child!.size.width / 2, pathCenter.dy - (child!.size.height + triangleHeight) / 2, @@ -621,14 +672,10 @@ class _CoreTooltipRenderBox extends RenderProxyBox { return; } - final double halfTooltipHeight = child!.size.height / 2; - context.canvas.save(); context.canvas.translate(_nosePosition!.dx, _nosePosition!.dy); context.canvas.scale(_state._animation.value); - final double centerY = _triangleHeight + halfTooltipHeight; - context.canvas - .translate(0.0, centerY * (_effectivePreferTooltipOnTop! ? -1 : 1)); + context.canvas.translate(-_nosePosition!.dx, -_nosePosition!.dy); // In web HTML rendering, fill color clipped half of its tooltip's size. // To avoid this issue we are drawing stroke before fill. // Due to this, half of the stroke width only diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart index e6c559bc7..d274f22e3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/data_label.dart @@ -1,4 +1,5 @@ import 'dart:collection'; +import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -606,6 +607,9 @@ class CartesianChartDataLabelPositioned Offset offset = Offset.zero; Size size = Size.zero; + Rect bounds = Rect.zero; + Rect rotatedBounds = Rect.zero; + bool isVisible = true; ChartDataLabelAlignment labelAlignment = ChartDataLabelAlignment.auto; @override @@ -736,7 +740,8 @@ class _CartesianDataLabelContainerState int seriesIndex, ChartDataPointType position, ) { - final num value = stackedYValues != null + final DataLabelSettings settings = widget.settings; + final num value = stackedYValues != null && !settings.showCumulativeValues ? stackedYValues![pointIndex] : yLists![yIndex][pointIndex]; final String formattedText = formatNumericValue(value, renderer!.yAxis); @@ -797,11 +802,16 @@ class _CartesianDataLabelContainerState (renderer!.emptyPointSettings.mode == EmptyPointMode.drop || renderer!.emptyPointSettings.mode == EmptyPointMode.gap); + final bool hasSortedIndexes = renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + final int start = renderer!.visibleIndexes[0]; final int end = renderer!.visibleIndexes[1]; final int xLength = actualXValues.length; for (int i = start; i <= end && i < xLength; i++) { - _obtainLabel(i, actualXValues, yLength, callback, add, isEmptyMode); + _obtainLabel(i, actualXValues, yLength, callback, add, isEmptyMode, + hasSortedIndexes); } } @@ -823,10 +833,15 @@ class _CartesianDataLabelContainerState (renderer!.emptyPointSettings.mode == EmptyPointMode.drop || renderer!.emptyPointSettings.mode == EmptyPointMode.gap); + final bool hasSortedIndexes = renderer!.sortingOrder != SortingOrder.none && + sortedIndexes != null && + sortedIndexes!.isNotEmpty; + final int xLength = actualXValues.length; for (final int index in renderer!.visibleIndexes) { if (index < xLength) { - _obtainLabel(index, actualXValues, yLength, callback, add, isEmptyMode); + _obtainLabel(index, actualXValues, yLength, callback, add, isEmptyMode, + hasSortedIndexes); } } } @@ -838,11 +853,13 @@ class _CartesianDataLabelContainerState _ChartDataLabelWidgetBuilder callback, Function(CartesianChartDataLabelPositioned) add, bool isEmptyMode, + bool hasSortedIndexes, ) { if (isEmptyMode && renderer!.emptyPointIndexes.contains(index)) { return; } + final int pointIndex = hasSortedIndexes ? sortedIndexes![index] : index; final num x = xValues![index]; for (int k = 0; k < yLength; k++) { final List yValues = yLists![k]; @@ -854,10 +871,10 @@ class _CartesianDataLabelContainerState dataPointIndex: index, position: position, child: callback( - widget.dataSource[index], + widget.dataSource[pointIndex], k, widget.series, - index, + pointIndex, renderer!.index, position, ), @@ -902,7 +919,7 @@ class _CartesianDataLabelContainerState } if (xValues != null && xValues!.isNotEmpty) { - if (renderer!.hasLinearData) { + if (renderer!.canFindLinearVisibleIndexes) { _buildLinearDataLabels(callback, add); } else { _buildNonLinearDataLabels(callback, add); @@ -1051,6 +1068,7 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { while (child != null) { final ChartElementParentData currentChildData = child.parentData! as ChartElementParentData; + currentChildData.isVisible = true; final RenderBox? nextSibling = currentChildData.nextSibling; ChartElementParentData? nextChildData; if (nextSibling != null) { @@ -1073,6 +1091,10 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { ); currentChildData.offset += _invokeDataLabelRender(currentChildData.dataPointIndex); + currentChildData.bounds = + _calculateBounds(child.size, currentChildData.offset); + currentChildData.rotatedBounds = + _calculateRotatedBounds(currentChildData.bounds); child = nextSibling; previousChildData = currentChildData; } @@ -1080,6 +1102,7 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { ChartElementParentData? previousLabelData; ChartElementParentData? nextLabelData; for (final CartesianChartDataLabelPositioned currentLabel in labels!) { + currentLabel.isVisible = true; final ChartElementParentData currentLabelData = nextLabelData ?? ChartElementParentData() ..x = currentLabel.x @@ -1112,9 +1135,15 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { nextLabelData, currentLabel.size, ); + currentLabel.bounds = + _calculateBounds(currentLabel.size, currentLabel.offset); + currentLabel.rotatedBounds = + _calculateRotatedBounds(currentLabel.bounds); previousLabelData = currentLabelData; } } + + _handleLabelIntersectAction(); } Offset _invokeDataLabelRender(int pointIndex, [DataLabelText? details]) { @@ -1147,11 +1176,12 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { } Offset _calculateAlignedPosition( - ChartDataLabelAlignment alignment, - ChartElementParentData? previous, - ChartElementParentData current, - ChartElementParentData? next, - Size size) { + ChartDataLabelAlignment alignment, + ChartElementParentData? previous, + ChartElementParentData current, + ChartElementParentData? next, + Size size, + ) { final Offset position = series!.dataLabelPosition(current, alignment, size); ChartAlignment? xAlignment; @@ -1223,6 +1253,198 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { return Offset(labelX, labelY); } + Rect _calculateBounds(Size childSize, Offset offset) { + return Rect.fromLTWH( + offset.dx + settings.offset.dx, + offset.dy - settings.offset.dy, + childSize.width + settings.margin.horizontal, + childSize.height + settings.margin.vertical, + ); + } + + Rect _calculateRotatedBounds(Rect labelBounds) { + final center = labelBounds.center; + final radius = settings.angle * pi / 180; + final corner1 = _rotatePoint(labelBounds.topLeft, center, radius); + final corner2 = _rotatePoint(labelBounds.topRight, center, radius); + final corner3 = _rotatePoint(labelBounds.bottomRight, center, radius); + final corner4 = _rotatePoint(labelBounds.bottomLeft, center, radius); + + final left = min(corner1.dx, min(corner2.dx, min(corner3.dx, corner4.dx))); + final right = max(corner1.dx, max(corner2.dx, max(corner3.dx, corner4.dx))); + final top = min(corner1.dy, min(corner2.dy, min(corner3.dy, corner4.dy))); + final bottom = + max(corner1.dy, max(corner2.dy, max(corner3.dy, corner4.dy))); + + return Rect.fromLTWH(left, top, right - left, bottom - top); + } + + Offset _rotatePoint(Offset point, Offset center, double radius) { + final double dx = point.dx - center.dx; + final double dy = point.dy - center.dy; + return Offset( + dx * cos(radius) - dy * sin(radius) + center.dx, + dx * sin(radius) + dy * cos(radius) + center.dy, + ); + } + + void _handleLabelIntersectAction() { + if (series!.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + if (childCount > 0) { + _handleLabelIntersectActionForWidgets(); + } else if (labels != null) { + _handleLabelIntersectActionForLabels(); + } + } + } + + void _handleLabelIntersectActionForWidgets() { + RenderBox? child = firstChild; + while (child != null) { + final ChartElementParentData currentChildData = + child.parentData! as ChartElementParentData; + if (!currentChildData.isVisible) { + child = currentChildData.nextSibling; + continue; + } + + RenderBox? nextSibling = currentChildData.nextSibling; + ChartElementParentData? nextChildData; + currentChildData.isVisible = true; + while (nextSibling != null) { + nextChildData = nextSibling.parentData! as ChartElementParentData; + if (currentChildData.rotatedBounds + .overlaps(nextChildData.rotatedBounds)) { + nextChildData.isVisible = false; + } + nextSibling = nextChildData.nextSibling; + } + child = currentChildData.nextSibling; + } + } + + void _handleLabelIntersectActionForLabels() { + for (final CartesianChartDataLabelPositioned label in labels!) { + if (!label.isVisible) { + continue; + } + CartesianChartDataLabelPositioned? nextLabel = label.next; + while (nextLabel != null) { + if (!label.rotatedBounds.topLeft.isNaN && + !label.rotatedBounds.bottomRight.isNaN && + label.rotatedBounds.overlaps(nextLabel.rotatedBounds)) { + nextLabel.isVisible = false; + } + nextLabel = nextLabel.next; + } + } + } + + @override + void handleMultiSeriesDataLabelCollisions() { + if (childCount > 0) { + _handleMultiSeriesDataLabelCollisionsForWidgets(); + } else if (labels != null) { + _handleMultiSeriesDataLabelCollisionsForLabels(); + } + } + + void _handleMultiSeriesDataLabelCollisionsForWidgets() { + series?.parent?.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.index != series!.index && + child.index > series!.index && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + final RenderBox? nextSeriesDataLabelRenderBox = + child.dataLabelContainer?.child; + + RenderBox? currentChild = firstChild; + while (currentChild != null) { + final ChartElementParentData currentChildData = + currentChild.parentData! as ChartElementParentData; + if (!currentChildData.isVisible) { + currentChild = currentChildData.nextSibling; + continue; + } + + nextSeriesDataLabelRenderBox + ?.visitChildren((RenderObject nextSeriesDataLabel) { + final RenderCartesianDataLabelStack? + nextSeriesDataLabelStack = + nextSeriesDataLabel as RenderCartesianDataLabelStack; + if (nextSeriesDataLabelStack!.childCount > 0) { + RenderBox? nextChild = nextSeriesDataLabelStack.firstChild; + while (nextChild != null) { + final ChartElementParentData nextChildData = + nextChild.parentData! as ChartElementParentData; + if (!nextChildData.isVisible) { + nextChild = nextChildData.nextSibling; + continue; + } + + if (currentChildData.rotatedBounds + .overlaps(nextChildData.rotatedBounds)) { + nextChildData.isVisible = false; + } + nextChild = nextChildData.nextSibling; + } + } + }); + currentChild = currentChildData.nextSibling; + } + } + }); + } + + void _handleMultiSeriesDataLabelCollisionsForLabels() { + series?.parent?.visitChildren((RenderObject child) { + if (child is CartesianSeriesRenderer && + child.controller.isVisible && + child.index != series!.index && + child.index > series!.index && + child.dataLabelSettings.isVisible && + child.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.none) { + final RenderBox? nextSeriesDataLabelRenderBox = + child.dataLabelContainer?.child; + for (final CartesianChartDataLabelPositioned currentLabel in labels!) { + if (!currentLabel.isVisible) { + continue; + } + + nextSeriesDataLabelRenderBox + ?.visitChildren((RenderObject nextSeriesDataLabel) { + final RenderCartesianDataLabelStack? + nextSeriesDataLabelStack = + nextSeriesDataLabel as RenderCartesianDataLabelStack; + final LinkedList? nextLabels = + nextSeriesDataLabelStack!.labels; + if (nextLabels != null && nextLabels.isNotEmpty) { + for (final CartesianChartDataLabelPositioned nextLabel + in nextLabels) { + if (!nextLabel.isVisible) { + continue; + } + + if (!currentLabel.rotatedBounds.topLeft.isNaN && + !currentLabel.rotatedBounds.bottomRight.isNaN && + currentLabel.rotatedBounds + .overlaps(nextLabel.rotatedBounds)) { + nextLabel.isVisible = false; + break; + } + } + } + }); + } + } + }); + } + @override void paint(PaintingContext context, Offset offset) { if (series == null || series!.xAxis == null || series!.yAxis == null) { @@ -1240,7 +1462,7 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { while (child != null) { final ChartElementParentData childParentData = child.parentData! as ChartElementParentData; - if (!childParentData.offset.isNaN) { + if (!childParentData.offset.isNaN && childParentData.isVisible) { context.paintChild(child, childParentData.offset + marginOffset); } child = childParentData.nextSibling; @@ -1252,7 +1474,7 @@ class RenderCartesianDataLabelStack extends RenderChartElementStack { ..strokeWidth = settings.borderWidth ..style = PaintingStyle.stroke; for (final CartesianChartDataLabelPositioned label in labels!) { - if (label.offset.isNaN) { + if (label.offset.isNaN || !label.isVisible) { continue; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart index b63b49d12..91cbf542f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/element_widget.dart @@ -19,6 +19,8 @@ mixin ChartElementParentDataMixin { List? stackedYValues; + List? sortedIndexes; + CurvedAnimation? animation; } @@ -63,6 +65,10 @@ class RenderChartFadeTransition extends RenderAnimatedOpacity { void handleTapUp(Offset localPosition) { (child as RenderChartElementStack?)?.handleTapUp(localPosition); } + + void handleMultiSeriesDataLabelCollisions() { + (child as RenderChartElementStack?)?.handleMultiSeriesDataLabelCollisions(); + } } class ChartElementLayoutBuilder @@ -116,6 +122,7 @@ class RenderChartElementLayoutBuilder extends RenderBox ..xValues = xValues ..yLists = yLists ..stackedYValues = stackedYValues + ..sortedIndexes = sortedIndexes ..sbsInfo = sbsInfo ..animation = animation; rebuildIfNecessary(); @@ -135,6 +142,11 @@ class RenderChartElementLayoutBuilder extends RenderBox (child as RenderChartFadeTransition?)?.handleTapUp(localPosition); } + void handleMultiSeriesDataLabelCollisions() { + (child as RenderChartFadeTransition?) + ?.handleMultiSeriesDataLabelCollisions(); + } + @override void paint(PaintingContext context, Offset offset) { if (child != null) { @@ -149,6 +161,9 @@ class ChartElementParentData extends ContainerBoxParentData { int dataPointIndex = -1; ChartDataPointType position = ChartDataPointType.y; ChartDataLabelAlignment labelAlignment = ChartDataLabelAlignment.auto; + Rect bounds = Rect.zero; + Rect rotatedBounds = Rect.zero; + bool isVisible = true; } class ChartElementStack extends MultiChildRenderObjectWidget { @@ -182,4 +197,8 @@ class RenderChartElementStack extends RenderBox void handlePointerHover(Offset localPosition) {} void handleTapUp(Offset localPosition) {} + + // Once all cartesian series layouts are completed, use this method to + // handle the collisions of data labels across multiple series. + void handleMultiSeriesDataLabelCollisions() {} } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart index 4365e4ac7..c03e9cd5c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/common/marker.dart @@ -421,7 +421,8 @@ class _MarkerContainerState extends State> return renderer!.emptyPointSettings.color; } else if (renderer!.markerSettings.borderColor != null) { return renderer!.markerSettings.borderColor!; - } else if (renderer!.pointColors.isNotEmpty) { + } else if (renderer!.pointColors.isNotEmpty && + renderer!.pointColors[dataPointIndex] != null) { return renderer!.pointColors[dataPointIndex]; } else { return renderer!.color ?? renderer!.paletteColor; @@ -444,7 +445,7 @@ class _MarkerContainerState extends State> xValues != null && xValues!.isNotEmpty && renderer!.controller.isVisible) { - if (renderer!.hasLinearData) { + if (renderer!.canFindLinearVisibleIndexes) { _buildLinearMarkers(context); } else { _buildNonLinearMarkers(context); @@ -576,7 +577,7 @@ class _RenderMarkerStack extends RenderChartElementStack { } fillPaint.color = marker.color ?? Colors.transparent; - strokePaint.color = marker.borderColor!; + strokePaint.color = marker.borderColor ?? Colors.transparent; strokePaint.strokeWidth = marker.borderWidth; if (settings.borderColor != null) { strokePaint.shader = null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart index be3c3000d..13b968f77 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/funnel_chart.dart @@ -13,6 +13,7 @@ import 'common/title.dart'; import 'interactions/behavior.dart'; import 'interactions/tooltip.dart'; import 'series/funnel_series.dart'; +import 'theme.dart'; import 'utils/enum.dart'; import 'utils/helper.dart'; import 'utils/typedef.dart'; @@ -48,18 +49,7 @@ class SfFunnelChart extends StatefulWidget { this.onChartTouchInteractionUp, this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], + this.palette, this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), this.series = const FunnelSeries(), this.title = const ChartTitle(), @@ -185,7 +175,7 @@ class SfFunnelChart extends StatefulWidget { /// ); /// } /// ``` - final List palette; + final List? palette; /// Customizes the tooltip in chart. /// @@ -417,34 +407,50 @@ class SfFunnelChartState extends State late ThemeData _themeData; SfLocalizations? _localizations; - SfChartThemeData _updateThemeData(BuildContext context) { - _chartThemeData = SfChartTheme.of(context); - _themeData = Theme.of(context); - _chartThemeData = _chartThemeData.copyWith( - backgroundColor: - widget.backgroundColor ?? _themeData.colorScheme.background, - titleBackgroundColor: - widget.title.backgroundColor ?? _chartThemeData.titleBackgroundColor, + SfChartThemeData _updateThemeData( + BuildContext context, SfChartThemeData effectiveChartThemeData) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, legendBackgroundColor: widget.legend.backgroundColor ?? - _chartThemeData.legendBackgroundColor, - titleTextStyle: _themeData.textTheme.bodyMedium! - .copyWith(color: _chartThemeData.titleTextColor, fontSize: 15) - .merge(_chartThemeData.titleTextStyle) + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor) + .merge(chartThemeData.titleTextStyle) .merge(widget.title.textStyle), - legendTitleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTitleColor) - .merge(_chartThemeData.legendTitleTextStyle) + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor) + .merge(chartThemeData.legendTitleTextStyle) .merge(widget.legend.title?.textStyle), - legendTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTextColor, fontSize: 13) - .merge(_chartThemeData.legendTextStyle) + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor) + .merge(chartThemeData.legendTextStyle) .merge(widget.legend.textStyle), - tooltipTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.tooltipLabelColor) - .merge(_chartThemeData.tooltipTextStyle) + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor) + .merge(chartThemeData.tooltipTextStyle) .merge(widget.tooltipBehavior?.textStyle), ); - return _chartThemeData; + return chartThemeData; } Widget _buildLegendItem(BuildContext context, int index) { @@ -508,8 +514,11 @@ class SfFunnelChartState extends State /// in [SfFunnelChart]. @override Widget build(BuildContext context) { - _chartThemeData = _updateThemeData(context); - final ThemeData themeData = Theme.of(context); + _themeData = Theme.of(context); + final SfChartThemeData effectiveChartThemeData = _themeData.useMaterial3 + ? SfChartThemeDataM3(context) + : SfChartThemeDataM2(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); final core.LegendPosition legendPosition = effectiveLegendPosition(widget.legend); final Axis orientation = @@ -569,20 +578,24 @@ class SfFunnelChartState extends State onLegendTapped: widget.onLegendTapped, onTooltipRender: widget.onTooltipRender, onDataLabelTapped: widget.onDataLabelTapped, - palette: widget.palette, + palette: widget.palette ?? + (_themeData.useMaterial3 + ? (effectiveChartThemeData as SfChartThemeDataM3).palette + : (effectiveChartThemeData as SfChartThemeDataM2).palette), selectionMode: SelectionType.point, selectionGesture: widget.selectionGesture, enableMultiSelection: widget.enableMultiSelection, tooltipBehavior: widget.tooltipBehavior, onSelectionChanged: widget.onSelectionChanged, chartThemeData: _chartThemeData, - themeData: themeData, + themeData: _themeData, children: [widget.series], ), if (widget.tooltipBehavior != null) BehaviorArea( tooltipKey: _tooltipKey, chartThemeData: _chartThemeData, + themeData: _themeData, tooltipBehavior: widget.tooltipBehavior, onTooltipRender: widget.onTooltipRender, children: [ @@ -594,8 +607,8 @@ class SfFunnelChartState extends State opacity: widget.tooltipBehavior!.opacity, borderColor: widget.tooltipBehavior!.borderColor, borderWidth: widget.tooltipBehavior!.borderWidth, - color: widget.tooltipBehavior!.color ?? - _chartThemeData.tooltipColor, + color: (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, showDuration: widget.tooltipBehavior!.duration.toInt(), shadowColor: widget.tooltipBehavior!.shadowColor, elevation: widget.tooltipBehavior!.elevation, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart index ed433d518..c5a6d4cfd 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/accumulation_distribution_indicator.dart @@ -2,9 +2,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -237,6 +237,8 @@ class ADIndicatorRenderer extends IndicatorRenderer { } void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; num yMinimum = double.infinity; num yMaximum = double.negativeInfinity; num sum = 0; @@ -254,12 +256,17 @@ class ADIndicatorRenderer extends IndicatorRenderer { } sum += value; + final double x = xValues[i].toDouble(); final double y = sum.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); - _signalLineActualValues.add(Offset(xValues[i].toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); } + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -303,16 +310,20 @@ class ADIndicatorRenderer extends IndicatorRenderer { final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); if (nearestPointIndex != -1) { final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], point: chartPoint, series: this, - pointIndex: nearestPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + segmentIndex: nearestPointIndex, + pointIndex: nearestPointIndex, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), color: signalLineColor, - ) + ), ]; } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart index c7667fc29..ae08d38e6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/atr_indicator.dart @@ -2,9 +2,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -198,8 +198,7 @@ class AtrIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -250,6 +249,8 @@ class AtrIndicatorRenderer extends IndicatorRenderer { } void _calculateSignalLineValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; num yMinimum = double.infinity; num yMaximum = double.negativeInfinity; @@ -259,7 +260,7 @@ class AtrIndicatorRenderer extends IndicatorRenderer { num average = 0; num sum = 0; for (int i = 0; i < dataCount; i++) { - final num x = xValues[i]; + final double x = xValues[i].toDouble(); final num high = _highValues[i].isNaN ? 0 : _highValues[i]; final num low = _lowValues[i].isNaN ? 0 : _lowValues[i]; final num highLow = high - low; @@ -282,22 +283,27 @@ class AtrIndicatorRenderer extends IndicatorRenderer { period; final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); - _signalLineActualValues.add(Offset(x.toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); } else { average = sum / period; if (i == period - 1) { final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); - _signalLineActualValues.add(Offset(x.toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); } } - averageOffsetValues - .add(Offset(xValues[i].toDouble(), average.toDouble())); + averageOffsetValues.add(Offset(x, average.toDouble())); } + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -340,6 +346,7 @@ class AtrIndicatorRenderer extends IndicatorRenderer { final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); if (nearestPointIndex != -1) { final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], @@ -347,8 +354,11 @@ class AtrIndicatorRenderer extends IndicatorRenderer { series: this, pointIndex: nearestPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), color: signalLineColor, + segmentIndex: nearestPointIndex, ) ]; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart index 4985dbd40..4860b30f4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/bollinger_bands_indicator.dart @@ -2,10 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; +import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -338,8 +339,7 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { set standardDeviation(int value) { if (_standardDeviation != value) { _standardDeviation = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -393,8 +393,7 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -441,6 +440,8 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { } void _calculateBollingerBandsValues() { + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; num yMinimum = double.infinity; num yMaximum = double.negativeInfinity; @@ -520,6 +521,8 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { final double minY = min(middle, min(upper, lower)); final double maxY = max(middle, max(upper, lower)); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, minY); yMaximum = max(yMaximum, maxY); @@ -534,6 +537,8 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { } } + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -550,7 +555,7 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { _upperLineChartPoints.clear(); _lowerLineChartPoints.clear(); - if (parent == null || yLists == null || yLists.isEmpty) { + if (parent == null || yLists.isEmpty) { return; } @@ -559,7 +564,7 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { } final int yLength = yLists.length; - if (positions == null || positions.length != yLength) { + if (positions.length != yLength) { return; } @@ -681,14 +686,18 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { _chartPoint(nearestPointIndex, 'upper'); final CartesianChartPoint lowerPoint = _chartPoint(nearestPointIndex, 'lower'); + final String bollingerText = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], point: bollingerPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: bollingerText, + header: tooltipHeaderText(bollingerPoint), + text: trackballText(bollingerPoint, bollingerText), color: signalLineColor, ), ChartTrackballInfo( @@ -696,8 +705,11 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { point: upperPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, - name: 'UpperLine', + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), color: _upperLineColor, ), ChartTrackballInfo( @@ -705,8 +717,11 @@ class BollingerIndicatorRenderer extends IndicatorRenderer { point: lowerPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, - name: 'LowerLine', + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), color: _lowerLineColor, ) ]; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart index 3e128073b..ae892769c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/ema_indicator.dart @@ -3,9 +3,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import '../axis/axis.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -243,8 +243,7 @@ class EmaIndicatorRenderer extends IndicatorRenderer { set valueField(String value) { if (_valueField != value) { _valueField = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -253,8 +252,7 @@ class EmaIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -336,14 +334,19 @@ class EmaIndicatorRenderer extends IndicatorRenderer { previousAverage; _yValues.add(yValue); + final double x = xValues[index].toDouble(); final double y = yValue.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); - _signalLineActualValues.add(Offset(xValues[index].toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); index++; } + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -412,14 +415,18 @@ class EmaIndicatorRenderer extends IndicatorRenderer { final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); if (nearestPointIndex != -1) { final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], point: chartPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), color: signalLineColor, ) ]; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart index fa0cff623..8feccc6aa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/macd_indicator.dart @@ -2,10 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; +import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -386,13 +387,17 @@ class MacdIndicatorRenderer extends IndicatorRenderer { final List> _histogramChartPoints = >[]; + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + int get period => _period; int _period = 14; set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -401,8 +406,7 @@ class MacdIndicatorRenderer extends IndicatorRenderer { set shortPeriod(int value) { if (_shortPeriod != value) { _shortPeriod = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -411,8 +415,7 @@ class MacdIndicatorRenderer extends IndicatorRenderer { set longPeriod(int value) { if (_longPeriod != value) { _longPeriod = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -439,8 +442,7 @@ class MacdIndicatorRenderer extends IndicatorRenderer { set macdType(MacdType value) { if (_macdType != value) { _macdType = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -520,6 +522,11 @@ class MacdIndicatorRenderer extends IndicatorRenderer { } } + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = min(yMin, _yMinimum); + yMax = max(yMax, _yMaximum); + populateChartPoints(); } @@ -563,74 +570,59 @@ class MacdIndicatorRenderer extends IndicatorRenderer { } void _calculateMACDValues(List macdPoints) { - num xMinimum = double.infinity; - num xMaximum = double.negativeInfinity; - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - int dataMACDIndex = longPeriod - 1; int macdIndex = 0; while (dataMACDIndex < dataCount) { final double x = xValues[dataMACDIndex].toDouble(); final double y = macdPoints[macdIndex].toDouble(); - xMinimum = min(xMinimum, x); - xMaximum = max(xMaximum, x); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); _yValues.add(y); _macdActualValues.add(Offset(x, y)); + dataMACDIndex++; macdIndex++; } - - xMin = min(xMin, xMinimum); - xMax = max(xMax, xMaximum); - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } void _calculateSignalValues(List signalEma) { - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - int index = longPeriod + period - 2; int signalIndex = 0; while (index < dataCount) { + final double x = xValues[index].toDouble(); final double y = signalEma[signalIndex].toDouble(); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); - _signalActualValues.add(Offset(xValues[index].toDouble(), y)); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + _signalActualValues.add(Offset(x, y)); index++; signalIndex++; } - - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } void _calculateHistogramValues(List macdPoints, List signalEma) { - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - int index = longPeriod + period - 2; int histogramIndex = 0; while (index < dataCount) { + final double x = xValues[index].toDouble(); final double y = macdPoints[histogramIndex + (period - 1)] - signalEma[histogramIndex].toDouble(); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); - _histogramActualValues.add(Offset(xValues[index].toDouble(), y)); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + _histogramActualValues.add(Offset(x, y)); index++; histogramIndex++; } - - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } @override @@ -645,7 +637,7 @@ class MacdIndicatorRenderer extends IndicatorRenderer { _macdChartPoints.clear(); _histogramChartPoints.clear(); - if (parent == null || yLists == null || yLists.isEmpty) { + if (parent == null || yLists.isEmpty) { return; } @@ -654,7 +646,7 @@ class MacdIndicatorRenderer extends IndicatorRenderer { } final int yLength = yLists.length; - if (positions == null || positions.length != yLength) { + if (positions.length != yLength) { return; } @@ -701,18 +693,23 @@ class MacdIndicatorRenderer extends IndicatorRenderer { if (macdPointIndex != -1) { final CartesianChartPoint macdPoint = _chartPoint(macdPointIndex, 'macd'); + final String text = defaultLegendItemText(); trackballInfo.add( ChartTrackballInfo( position: signalLinePoints[macdPointIndex], point: macdPoint, series: this, pointIndex: macdPointIndex, + segmentIndex: macdPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: text, + header: tooltipHeaderText(macdPoint), + text: trackballText(macdPoint, text), color: signalLineColor, ), ); } + final int macdLinePointIndex = _findNearestPoint(_macdPoints, position); if (macdLinePointIndex != -1) { final CartesianChartPoint macdLinePoint = @@ -723,13 +720,17 @@ class MacdIndicatorRenderer extends IndicatorRenderer { point: macdLinePoint, series: this, pointIndex: macdLinePointIndex, + segmentIndex: macdLinePointIndex, seriesIndex: index, - name: 'MacdLine', + name: trackballMACDLineText, + header: tooltipHeaderText(macdLinePoint), + text: trackballText(macdLinePoint, trackballMACDLineText), color: _macdLineColor, ), ); } } + if (macdType == MacdType.both || macdType == MacdType.histogram) { final int histogramPointIndex = _findNearestPoint( List.generate( @@ -748,8 +749,11 @@ class MacdIndicatorRenderer extends IndicatorRenderer { point: histogramPoint, series: this, pointIndex: histogramPointIndex, + segmentIndex: histogramPointIndex, seriesIndex: index, - name: 'Histogram', + name: trackballHistogramText, + header: tooltipHeaderText(histogramPoint), + text: trackballText(histogramPoint, trackballHistogramText), color: histogramPoint.y!.isNegative ? histogramNegativeColor : histogramPositiveColor, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart index 1fbfe6004..c11ec64a9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/momentum_indicator.dart @@ -2,10 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; +import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -282,8 +283,7 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -329,6 +329,8 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { _signalLineActualValues.clear(); _centerLineActualValues.clear(); + num xMinimum = double.infinity; + num xMaximum = double.negativeInfinity; num yMinimum = double.infinity; num yMaximum = double.negativeInfinity; @@ -336,6 +338,8 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { final int length = period; for (int i = 0; i < dataCount; i++) { final double x = xValues[i].toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, 100); yMaximum = max(yMaximum, 100); _centerLineActualValues.add(Offset(x, 100)); @@ -346,12 +350,16 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { continue; } final double y = value.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); _signalLineActualValues.add(Offset(x, y)); } } + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -413,14 +421,18 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { if (momentumPointIndex != -1) { final CartesianChartPoint momentumPoint = _chartPoint(momentumPointIndex, 'momentum'); + final String text = defaultLegendItemText(); trackballInfo.add( ChartTrackballInfo( position: signalLinePoints[momentumPointIndex], point: momentumPoint, series: this, pointIndex: momentumPointIndex, + segmentIndex: momentumPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: text, + header: tooltipHeaderText(momentumPoint), + text: trackballText(momentumPoint, text), color: signalLineColor, ), ); @@ -434,8 +446,11 @@ class MomentumIndicatorRenderer extends IndicatorRenderer { point: centerPoint, series: this, pointIndex: centerPointIndex, + segmentIndex: centerPointIndex, seriesIndex: index, - name: 'CenterLine', + name: trackballCenterText, + header: tooltipHeaderText(centerPoint), + text: trackballText(centerPoint, trackballCenterText), color: _centerLineColor, )); } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart index 6ac6764e9..7c34ce925 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/rsi_indicator.dart @@ -2,10 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; +import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -398,6 +399,11 @@ class RsiIndicatorRenderer extends IndicatorRenderer { List _lowValues = []; List _closeValues = []; + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + bool get showZones => _showZones; bool _showZones = true; set showZones(bool value) { @@ -412,8 +418,7 @@ class RsiIndicatorRenderer extends IndicatorRenderer { set overbought(double value) { if (_overbought != value) { _overbought = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -422,8 +427,7 @@ class RsiIndicatorRenderer extends IndicatorRenderer { set oversold(double value) { if (_oversold != value) { _oversold = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -468,8 +472,7 @@ class RsiIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -518,35 +521,32 @@ class RsiIndicatorRenderer extends IndicatorRenderer { _calculateSignalLineValues(); } + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = min(yMin, _yMinimum); + yMax = max(yMax, _yMaximum); + populateChartPoints(); } void _calculateZones() { if (showZones) { - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; for (int i = 0; i < dataCount; i++) { final double x = xValues[i].toDouble(); final double minY = min(overbought, oversold); final double maxY = max(overbought, oversold); - yMinimum = min(yMinimum, minY); - yMaximum = max(yMaximum, maxY); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, minY); + _yMaximum = max(_yMaximum, maxY); _upperLineValues.add(Offset(x, overbought)); _lowerLineValues.add(Offset(x, oversold)); } - - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } } void _calculateSignalLineValues() { - num xMinimum = double.infinity; - num xMaximum = double.negativeInfinity; - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - num previousClose = _closeValues[0]; if (previousClose.isNaN) { previousClose = 0; @@ -573,10 +573,10 @@ class RsiIndicatorRenderer extends IndicatorRenderer { final double x = xValues[period].toDouble(); final double y = value.toDouble(); - xMinimum = min(xMinimum, x); - xMaximum = max(xMaximum, x); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); _signalLineActualValues.add(Offset(x, y)); @@ -595,17 +595,15 @@ class RsiIndicatorRenderer extends IndicatorRenderer { previousClose = currentClose; final num value = 100 - (100 / (1 + (gain / loss))); + final double x = xValues[j].toDouble(); final double y = value.toDouble(); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); - _signalLineActualValues.add(Offset(xValues[j].toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); } - - xMin = min(xMin, xMinimum); - xMax = max(xMax, xMaximum); - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } @override @@ -672,16 +670,19 @@ class RsiIndicatorRenderer extends IndicatorRenderer { >[]; final int rsiPointIndex = _findNearestPoint(signalLinePoints, position); if (rsiPointIndex != -1) { - final CartesianChartPoint rsiPointInfo = - _chartPoint(rsiPointIndex, 'rsi'); + final CartesianChartPoint rsiPoint = _chartPoint(rsiPointIndex, 'rsi'); + final String rsiText = defaultLegendItemText(); trackballInfo.add( ChartTrackballInfo( position: signalLinePoints[rsiPointIndex], - point: rsiPointInfo, + point: rsiPoint, series: this, pointIndex: rsiPointIndex, + segmentIndex: rsiPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: rsiText, + header: tooltipHeaderText(rsiPoint), + text: trackballText(rsiPoint, rsiText), color: signalLineColor, ), ); @@ -689,32 +690,38 @@ class RsiIndicatorRenderer extends IndicatorRenderer { if (showZones) { final int upperPointIndex = _findNearestPoint(_upperLinePoints, position); if (upperPointIndex != -1) { - final CartesianChartPoint upperPointInfo = + final CartesianChartPoint upperPoint = _chartPoint(upperPointIndex, 'upper'); trackballInfo.add( ChartTrackballInfo( position: _upperLinePoints[upperPointIndex], - point: upperPointInfo, + point: upperPoint, series: this, pointIndex: upperPointIndex, + segmentIndex: upperPointIndex, seriesIndex: index, - name: 'UpperLine', + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), color: _upperLineColor, ), ); } final int lowerPointIndex = _findNearestPoint(_lowerLinePoints, position); if (lowerPointIndex != -1) { - final CartesianChartPoint lowerPointInfo = + final CartesianChartPoint lowerPoint = _chartPoint(lowerPointIndex, 'lower'); trackballInfo.add( ChartTrackballInfo( position: _lowerLinePoints[lowerPointIndex], - point: lowerPointInfo, + point: lowerPoint, series: this, pointIndex: lowerPointIndex, + segmentIndex: lowerPointIndex, seriesIndex: index, - name: 'LowerLine', + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), color: _lowerLineColor, ), ); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart index 92e17bdd5..aaec23295 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/sma_indicator.dart @@ -2,9 +2,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -237,8 +237,7 @@ class SmaIndicatorRenderer extends IndicatorRenderer { set valueField(String value) { if (_valueField != value) { _valueField = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -247,8 +246,7 @@ class SmaIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -328,16 +326,19 @@ class SmaIndicatorRenderer extends IndicatorRenderer { average = sum / period; _yValues.add(average); + final double x = xValues[j].toDouble(); final double y = average.toDouble(); + xMinimum = min(xMinimum, x); + xMaximum = max(xMaximum, x); yMinimum = min(yMinimum, y); yMaximum = max(yMaximum, y); - _signalLineActualValues.add(Offset(xValues[j].toDouble(), y)); + _signalLineActualValues.add(Offset(x, y)); j++; } - xMin = min(xMin, xMinimum); - xMax = max(xMax, xMaximum); + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -397,15 +398,19 @@ class SmaIndicatorRenderer extends IndicatorRenderer { final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); if (nearestPointIndex != -1) { final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], point: chartPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), color: signalLineColor, - name: defaultLegendItemText(), ) ]; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart index 7f6b4a412..4afaad96a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/stochastic_indicator.dart @@ -3,10 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; +import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -521,6 +522,11 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { final List> _periodChartPoints = >[]; + num _xMinimum = double.infinity; + num _xMaximum = double.negativeInfinity; + num _yMinimum = double.infinity; + num _yMaximum = double.negativeInfinity; + bool get showZones => _showZones; bool _showZones = true; set showZones(bool value) { @@ -535,8 +541,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { set overbought(double value) { if (_overbought != value) { _overbought = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -545,8 +550,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { set oversold(double value) { if (_oversold != value) { _oversold = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -609,8 +613,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -619,8 +622,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { set kPeriod(num value) { if (_kPeriod != value) { _kPeriod = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -629,8 +631,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { set dPeriod(num value) { if (_dPeriod != value) { _dPeriod = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -682,27 +683,28 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { _calculateStochasticIndicatorValues(); } + xMin = _xMinimum.isInfinite ? xMin : _xMinimum; + xMax = _xMaximum.isInfinite ? xMax : _xMaximum; + yMin = min(yMin, _yMinimum); + yMax = max(yMax, _yMaximum); + populateChartPoints(); } void _calculateZones() { if (showZones) { - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - for (int i = 0; i < dataCount; i++) { final double x = xValues[i].toDouble(); final double minY = min(overbought, oversold); final double maxY = max(overbought, oversold); - yMinimum = min(yMinimum, minY); - yMaximum = max(yMaximum, maxY); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, minY); + _yMaximum = max(_yMaximum, maxY); _upperLineActualValues.add(Offset(x, overbought)); _lowerLineActualValues.add(Offset(x, oversold)); } - - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } } @@ -738,31 +740,21 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { tempCount = temp.length; } - num xMinimum = double.infinity; - num xMaximum = double.negativeInfinity; - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - final int total = count - 1; for (int i = 0; i < dataLength; i++) { if (!(i < total)) { final double x = data[i].dx; final double y = values[i - total].toDouble(); - xMinimum = min(xMinimum, x); - xMaximum = max(xMaximum, x); - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); final Offset offset = Offset(x, y); points.add(offset); data[i] = offset; } } - - xMin = min(xMin, xMinimum); - xMax = max(xMax, xMaximum); - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); } return points; @@ -773,9 +765,6 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { return []; } - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; - final List lowValues = List.filled(dataCount, -1); final List highValues = List.filled(dataCount, -1); final List closeValues = List.filled(dataCount, -1); @@ -794,13 +783,16 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { maxValues.add(0); minValues.add(0); + final double x = xValues[i].toDouble(); double y = _closeValues[i].toDouble(); if (y.isNaN) { y = 0.0; } - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); modifiedSourceValues.add(Offset(xValues[i].toDouble(), y)); } @@ -823,16 +815,16 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { top += closeValues[i] - minValues[i]; bottom += maxValues[i] - minValues[i]; + final double x = xValues[i].toDouble(); final double y = (top / bottom) * 100; - yMinimum = min(yMinimum, y); - yMaximum = max(yMaximum, y); - modifiedSourceValues.add(Offset(xValues[i].toDouble(), y)); + _xMinimum = min(_xMinimum, x); + _xMaximum = max(_xMaximum, x); + _yMinimum = min(_yMinimum, y); + _yMaximum = max(_yMaximum, y); + modifiedSourceValues.add(Offset(x, y)); } } - yMin = min(yMin, yMinimum); - yMax = max(yMax, yMaximum); - return modifiedSourceValues; } @@ -844,14 +836,18 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { if (stocasticPointIndex != -1) { final CartesianChartPoint stocasticPoint = _chartPoint(stocasticPointIndex, 'stocastic'); + final String stocasticText = defaultLegendItemText(); trackballInfo.add( ChartTrackballInfo( position: signalLinePoints[stocasticPointIndex], point: stocasticPoint, series: this, pointIndex: stocasticPointIndex, + segmentIndex: stocasticPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: stocasticText, + header: tooltipHeaderText(stocasticPoint), + text: trackballText(stocasticPoint, stocasticText), color: signalLineColor, ), ); @@ -866,8 +862,11 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { point: periodPoint, series: this, pointIndex: periodPointIndex, + segmentIndex: periodPointIndex, seriesIndex: index, - name: 'PeriodLine', + name: trackballPeriodLineText, + header: tooltipHeaderText(periodPoint), + text: trackballText(periodPoint, trackballPeriodLineText), color: periodLineColor, ), ); @@ -883,8 +882,11 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { point: upperPoint, series: this, pointIndex: upperPointIndex, + segmentIndex: upperPointIndex, seriesIndex: index, - name: 'UpperLine', + name: trackballUpperLineText, + header: tooltipHeaderText(upperPoint), + text: trackballText(upperPoint, trackballUpperLineText), color: upperLineColor, ), ); @@ -899,8 +901,11 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { point: lowerPoint, series: this, pointIndex: lowerPointIndex, + segmentIndex: lowerPointIndex, seriesIndex: index, - name: 'LowerLine', + name: trackballLowerLineText, + header: tooltipHeaderText(lowerPoint), + text: trackballText(lowerPoint, trackballLowerLineText), color: lowerLineColor, ), ); @@ -999,7 +1004,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { chartPoints.clear(); _periodChartPoints.clear(); - if (parent == null || yLists == null || yLists.isEmpty) { + if (parent == null || yLists.isEmpty) { return; } @@ -1008,7 +1013,7 @@ class StochasticIndicatorRenderer extends IndicatorRenderer { } final int yLength = yLists.length; - if (positions == null || positions.length != yLength) { + if (positions.length != yLength) { return; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart index 480d72bde..77bba9cef 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/technical_indicator.dart @@ -1,17 +1,20 @@ -import 'dart:math'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; import '../common/core_legend.dart'; import '../common/legend.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -673,6 +676,9 @@ abstract class IndicatorRenderer extends RenderBox @override bool get sizedByParent => true; + @override + RenderIndicatorArea? get parent => super.parent as RenderIndicatorArea?; + /// The [TickerProvider] for the [AnimationController] that /// runs the animation. TickerProvider? get vsync => _vsync; @@ -865,8 +871,15 @@ abstract class IndicatorRenderer extends RenderBox @override void performUpdate() { if (parent != null && parent is RenderIndicatorArea) { - final RenderIndicatorArea parentBox = parent! as RenderIndicatorArea; - dependent = parentBox.series[seriesName]; + dependent = parent!.series[seriesName]; + } + + markNeedsPopulateAndLayout(); + } + + void markNeedsPopulateAndLayout() { + if (xAxis == null || yAxis == null) { + return; } populateDataSource(); @@ -908,19 +921,8 @@ abstract class IndicatorRenderer extends RenderBox xValues.clear(); late num currentX; - num xMinimum = double.infinity; - num xMaximum = double.negativeInfinity; - num yMinimum = double.infinity; - num yMaximum = double.negativeInfinity; + final Function(int, D) preferredXValue = _preferredXValue(); final Function(D? value, num x) addXValue = _addRawAndPreferredXValue; - Function(D, int) preferredValue; - if (D == DateTime) { - preferredValue = _valueFromDateTime; - } else if (D == String) { - preferredValue = _valueFromString; - } else { - preferredValue = _valueFromNum; - } for (int i = 0; i < length; i++) { final D? rawX = xPath(i); @@ -928,38 +930,43 @@ abstract class IndicatorRenderer extends RenderBox continue; } - currentX = preferredValue(rawX, i); + currentX = preferredXValue(i, rawX); addXValue(rawX, currentX); - xMinimum = min(xMinimum, currentX); - xMaximum = max(xMaximum, currentX); for (int j = 0; j < yPathLength; j++) { final ChartIndexedValueMapper? yPath = yPaths[j]; if (yPath == null) { continue; } - final num? yValue = yPath(i); - yList[j]!.add(yValue ?? double.nan); - if (yValue != null) { - yMinimum = min(yMinimum, yValue); - yMaximum = max(yMaximum, yValue); - } + yList[j]!.add(yPath(i) ?? double.nan); } } dataCount = xValues.length; } - num _valueFromDateTime(D value, int index) { - final DateTime date = value as DateTime; - return date.millisecondsSinceEpoch; + Function(int, D) _preferredXValue() { + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + return _valueAsNum; + } else if (xAxis is RenderDateTimeAxis) { + return _dateToMilliseconds; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + return _valueToIndex; + } + return _valueAsNum; } - num _valueFromNum(D value, int index) { + num _valueAsNum(int index, D value) { return value as num; } - num _valueFromString(D value, int index) { + num _dateToMilliseconds(int index, D value) { + final DateTime date = value as DateTime; + return date.millisecondsSinceEpoch; + } + + num _valueToIndex(int index, D value) { return index; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart index 45427d1c5..db8d7d4fe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/indicators/tma_indicator.dart @@ -2,9 +2,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; -import '../interactions/trackball.dart'; import '../series/chart_series.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -236,8 +236,7 @@ class TmaIndicatorRenderer extends IndicatorRenderer { set valueField(String value) { if (_valueField != value) { _valueField = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -246,8 +245,7 @@ class TmaIndicatorRenderer extends IndicatorRenderer { set period(int value) { if (_period != value) { _period = value; - populateDataSource(); - markNeedsLayout(); + markNeedsPopulateAndLayout(); } } @@ -351,8 +349,8 @@ class TmaIndicatorRenderer extends IndicatorRenderer { l++; } - xMin = min(xMin, xMinimum); - xMax = max(xMax, xMaximum); + xMin = xMinimum.isInfinite ? xMin : xMinimum; + xMax = xMaximum.isInfinite ? xMax : xMaximum; yMin = min(yMin, yMinimum); yMax = max(yMax, yMaximum); } @@ -385,14 +383,18 @@ class TmaIndicatorRenderer extends IndicatorRenderer { final int nearestPointIndex = _findNearestPoint(signalLinePoints, position); if (nearestPointIndex != -1) { final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + final String text = defaultLegendItemText(); return >[ ChartTrackballInfo( position: signalLinePoints[nearestPointIndex], point: chartPoint, series: this, pointIndex: nearestPointIndex, + segmentIndex: nearestPointIndex, seriesIndex: index, - name: defaultLegendItemText(), + name: text, + header: tooltipHeaderText(chartPoint), + text: trackballText(chartPoint, text), color: signalLineColor, ) ]; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart index 7b484803b..5bc6ceefa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/behavior.dart @@ -1,49 +1,24 @@ import 'dart:async'; -import 'dart:math'; -import 'dart:ui' as dart_ui; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart' hide Image; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:intl/intl.dart' hide TextDirection; -import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; -import '../../sparkline/utils/helper.dart'; import '../axis/axis.dart'; -import '../axis/category_axis.dart'; -import '../axis/datetime_axis.dart'; -import '../axis/datetime_category_axis.dart'; -import '../axis/logarithmic_axis.dart'; -import '../axis/numeric_axis.dart'; import '../base.dart'; +import '../behaviors/crosshair.dart'; +import '../behaviors/trackball.dart'; +import '../behaviors/zooming.dart'; import '../common/callbacks.dart'; -import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; -import '../common/interactive_tooltip.dart'; -import '../common/marker.dart'; -import '../indicators/technical_indicator.dart'; -import '../series/bar_series.dart'; -import '../series/box_and_whisker_series.dart'; -import '../series/bubble_series.dart'; -import '../series/candle_series.dart'; import '../series/chart_series.dart'; -import '../series/column_series.dart'; -import '../series/hilo_open_close_series.dart'; -import '../series/hilo_series.dart'; -import '../series/histogram_series.dart'; -import '../series/range_column_series.dart'; -import '../series/scatter_series.dart'; -import '../series/spline_series.dart'; -import '../series/waterfall_series.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; -import '../utils/helper.dart'; import '../utils/typedef.dart'; import 'tooltip.dart'; -import 'trackball.dart'; class BehaviorArea extends MultiChildRenderObjectWidget { const BehaviorArea({ @@ -64,6 +39,7 @@ class BehaviorArea extends MultiChildRenderObjectWidget { this.onCrosshairPositionChanging, this.onTooltipRender, required this.chartThemeData, + required this.themeData, super.children, this.trackballBuilder, this.textDirection, @@ -78,6 +54,7 @@ class BehaviorArea extends MultiChildRenderObjectWidget { final ZoomPanBehavior? zoomPanBehavior; final TrackballBehavior? trackballBehavior; final SfChartThemeData chartThemeData; + final ThemeData? themeData; final ChartZoomingCallback? onZooming; final ChartZoomingCallback? onZoomStart; final ChartZoomingCallback? onZoomEnd; @@ -108,6 +85,7 @@ class BehaviorArea extends MultiChildRenderObjectWidget { ..trackballBuilder = trackballBuilder ..onTooltipRender = onTooltipRender ..chartThemeData = chartThemeData + ..themeData = themeData ..textDirection = textDirection; } @@ -133,6 +111,7 @@ class BehaviorArea extends MultiChildRenderObjectWidget { ..trackballBuilder = trackballBuilder ..onTooltipRender = onTooltipRender ..chartThemeData = chartThemeData + ..themeData = themeData ..textDirection = textDirection; } } @@ -150,13 +129,15 @@ class RenderBehaviorArea extends RenderBox bool _crosshairEnabled = false; bool _trackballEnabled = false; bool _zoomingEnabled = false; - bool _performZoomThroughTouch = false; + bool performZoomThroughTouch = false; TooltipInfo? _previousTooltipInfo; RenderCartesianAxes? cartesianAxes; RenderIndicatorArea? indicatorArea; RenderChartPlotArea? plotArea; RenderLoadingIndicator? _loadingIndicator; + TooltipOpacityRenderBox? _tooltip; + TrackballOpacityRenderBox? _trackball; ChartZoomingCallback? onZooming; ChartZoomingCallback? onZoomStart; @@ -233,7 +214,7 @@ class RenderBehaviorArea extends RenderBox value.enableMouseWheelZooming || value.enableDoubleTapZooming || value.enableSelectionZooming); - _performZoomThroughTouch = value != null && + performZoomThroughTouch = value != null && (value.enablePinching || value.enableSelectionZooming); } } @@ -257,6 +238,13 @@ class RenderBehaviorArea extends RenderBox markNeedsPaint(); } + ThemeData? get themeData => _themeData; + ThemeData? _themeData; + set themeData(ThemeData? value) { + _themeData = value; + markNeedsPaint(); + } + RenderChartAxis? get xAxis => cartesianAxes?.axes[primaryXAxisName]; RenderChartAxis? get yAxis => cartesianAxes?.axes[primaryYAxisName]; @@ -300,6 +288,12 @@ class RenderBehaviorArea extends RenderBox if (child is RenderLoadingIndicator) { _loadingIndicator = child; } + if (child is TooltipOpacityRenderBox) { + _tooltip = child; + } + if (child is TrackballOpacityRenderBox) { + _trackball = child; + } } @override @@ -308,6 +302,12 @@ class RenderBehaviorArea extends RenderBox if (child is RenderLoadingIndicator) { _loadingIndicator = null; } + if (child is TooltipOpacityRenderBox) { + _tooltip = null; + } + if (child is TrackballOpacityRenderBox) { + _trackball = null; + } } @override @@ -334,12 +334,14 @@ class RenderBehaviorArea extends RenderBox @override bool hitTest(BoxHitTestResult result, {required Offset position}) { - bool isHit = false; + bool isLoadingIndicatorHit = false; + bool isTooltipHit = false; + bool isTrackballHit = false; if (size.contains(position)) { if (_loadingIndicator != null) { final StackParentData childParentData = _loadingIndicator!.parentData! as StackParentData; - isHit = result.addWithPaintOffset( + isLoadingIndicatorHit = result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset transformed) { @@ -348,202 +350,106 @@ class RenderBehaviorArea extends RenderBox ); } - return isHit || _crosshairEnabled || _trackballEnabled || _zoomingEnabled; - } - return false; - } - - void handlePointerEnter(PointerEnterEvent details) { - if (_crosshairEnabled && - crosshairBehavior!.activationMode == ActivationMode.singleTap) { - _showCrosshair(globalToLocal(details.position)); - } - if (_trackballEnabled && - trackballBehavior!.activationMode == ActivationMode.singleTap) { - _showTrackball(globalToLocal(details.position)); - } - } - - void handlePointerDown(PointerDownEvent details) { - if (zoomPanBehavior != null) { - zoomPanBehavior!._startPinchZooming(details); - } - _loadingIndicator?.handlePointerDown(details); - } + if (_tooltip != null) { + final StackParentData childParentData = + _tooltip!.parentData! as StackParentData; + isTooltipHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return _tooltip!.hitTest(result, position: transformed); + }, + ); + } - void handlePointerMove(PointerMoveEvent details) { - if (crosshairBehavior?.activationMode == ActivationMode.singleTap) { - _showCrosshair(globalToLocal(details.position)); - } - if (trackballBehavior?.activationMode == ActivationMode.singleTap) { - _showTrackball(globalToLocal(details.position)); - } - if (zoomPanBehavior != null && zoomPanBehavior!.enablePinching) { - _performPinchZoomUpdate(details); - } - } + if (_trackball != null) { + final StackParentData childParentData = + _trackball!.parentData! as StackParentData; + isTrackballHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return _trackball!.hitTest(result, position: transformed); + }, + ); + } - void handlePointerHover(PointerHoverEvent details) { - if (_crosshairEnabled && - crosshairBehavior!.activationMode == ActivationMode.singleTap) { - _showCrosshair(globalToLocal(details.position)); - } - if (_trackballEnabled && - trackballBehavior!.activationMode == ActivationMode.singleTap) { - _showTrackball(globalToLocal(details.position)); + return isLoadingIndicatorHit || + isTooltipHit || + isTrackballHit || + _crosshairEnabled || + _trackballEnabled || + _zoomingEnabled; } + return false; } - void handlePointerUp(PointerUpEvent details) { - if (trackballBehavior?.activationMode != ActivationMode.doubleTap) { - _hideTrackball(); - } - if (_performZoomThroughTouch) { - zoomPanBehavior!._endPinchZooming(details); - } + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + crosshairBehavior?.handleEvent(event, entry); + trackballBehavior?.handleEvent(event, entry); + zoomPanBehavior?.handleEvent(event, entry); } - void handlePointerCancel(PointerCancelEvent details) { - _hideCrosshair(immediately: true); - _hideTrackball(immediately: true); + void handlePointerEnter(PointerEnterEvent details) { + crosshairBehavior?.handlePointerEnter(details); + trackballBehavior?.handlePointerEnter(details); } void handlePointerExit(PointerExitEvent details) { - _hideCrosshair(immediately: true); - _hideTrackball(immediately: true); + crosshairBehavior?.handlePointerExit(details); + trackballBehavior?.handlePointerExit(details); } void handleLongPressStart(LongPressStartDetails details) { - if (crosshairBehavior?.activationMode == ActivationMode.longPress) { - _showCrosshair(globalToLocal(details.globalPosition)); - } - if (trackballBehavior?.activationMode == ActivationMode.longPress) { - _showTrackball(globalToLocal(details.globalPosition)); - } - if (zoomPanBehavior != null && zoomPanBehavior!.enableSelectionZooming) { - hideInteractiveTooltip(); - zoomPanBehavior!._longPressStart(globalToLocal(details.globalPosition)); - } + crosshairBehavior?.handleLongPressStart(details); + trackballBehavior?.handleLongPressStart(details); + zoomPanBehavior?.handleLongPressStart(details); } void handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - if (crosshairBehavior?.activationMode == ActivationMode.longPress) { - _showCrosshair(globalToLocal(details.globalPosition)); - } - if (trackballBehavior?.activationMode == ActivationMode.longPress) { - _showTrackball(globalToLocal(details.globalPosition)); - } - if (zoomPanBehavior != null && zoomPanBehavior!.enableSelectionZooming) { - final Offset position = globalToLocal(details.globalPosition); - zoomPanBehavior!._doSelectionZooming(position.dx, position.dy); - markNeedsPaint(); - } + crosshairBehavior?.handleLongPressMoveUpdate(details); + trackballBehavior?.handleLongPressMoveUpdate(details); + zoomPanBehavior?.handleLongPressMoveUpdate(details); } void handleLongPressEnd(LongPressEndDetails details) { - _hideCrosshair(); - _hideTrackball(); - if (zoomPanBehavior != null && zoomPanBehavior!.enableSelectionZooming) { - zoomPanBehavior!._longPressEnd(); - markNeedsPaint(); - } + crosshairBehavior?.handleLongPressEnd(details); + trackballBehavior?.handleLongPressEnd(details); + zoomPanBehavior?.handleLongPressEnd(details); } void handleTapDown(TapDownDetails details) { - if (crosshairBehavior?.activationMode == ActivationMode.singleTap) { - _showCrosshair(globalToLocal(details.globalPosition)); - } - if (trackballBehavior?.activationMode == ActivationMode.singleTap) { - _showTrackball(globalToLocal(details.globalPosition)); - } + crosshairBehavior?.handleTapDown(details); + trackballBehavior?.handleTapDown(details); } void handleTapUp(TapUpDetails details) { - _hideCrosshair(); - _hideTrackball(); + crosshairBehavior?.handleTapUp(details); + trackballBehavior?.handleTapUp(details); } void handleDoubleTap(Offset position) { - final Offset localPosition = globalToLocal(position); - if (crosshairBehavior?.activationMode == ActivationMode.doubleTap) { - _showCrosshair(localPosition); - _hideCrosshair(doubleTapHideDelay: 200); - } - if (trackballBehavior?.activationMode == ActivationMode.doubleTap) { - _showTrackball(localPosition); - _hideTrackball(doubleTapHideDelay: 200); - } - if (zoomPanBehavior != null && zoomPanBehavior!.enableDoubleTapZooming) { - hideInteractiveTooltip(); - zoomPanBehavior!._doubleTap(localPosition, paintBounds); - } + crosshairBehavior?.handleDoubleTap(position); + trackballBehavior?.handleDoubleTap(position); + zoomPanBehavior?.handleDoubleTap(position); } void handleScaleStart(ScaleStartDetails details) { - zoomPanBehavior?._startPanning(); + zoomPanBehavior?.handleScaleStart(details); _loadingIndicator?.handleScaleStart(details); } void handleScaleUpdate(ScaleUpdateDetails details) { - _performPanning(details); + zoomPanBehavior?.handleScaleUpdate(details); _loadingIndicator?.handleScaleUpdate(details); } void handleScaleEnd(ScaleEndDetails details) { - zoomPanBehavior?._endPanning(); + zoomPanBehavior?.handleScaleEnd(details); _loadingIndicator?.handleScaleEnd(details); } - void handlePanZoomUpdate(PointerEvent details) { - if (zoomPanBehavior != null && - attached && - zoomPanBehavior!.enableMouseWheelZooming) { - hideInteractiveTooltip(); - final Offset localPosition = globalToLocal(details.position); - if (details is PointerScrollEvent) { - zoomPanBehavior!._performMouseWheelZooming( - details, localPosition.dx, localPosition.dy, paintBounds); - } else { - zoomPanBehavior!._performMouseWheelZooming( - details, localPosition.dx, localPosition.dy, paintBounds); - } - } - } - - void _performPinchZoomUpdate(PointerMoveEvent event) { - if (_performZoomThroughTouch && zoomPanBehavior!.enablePinching) { - hideInteractiveTooltip(); - zoomPanBehavior!._zoom(event); - } - } - - void _performPanning(ScaleUpdateDetails details) { - if (zoomPanBehavior != null && zoomPanBehavior!.enablePanning) { - zoomPanBehavior!._pan(globalToLocal(details.focalPoint), paintBounds); - } - } - - void _showCrosshair(Offset localPosition) { - crosshairBehavior?.show(localPosition.dx, localPosition.dy, 'pixel'); - } - - void _hideCrosshair({int doubleTapHideDelay = 0, bool immediately = false}) { - if (crosshairBehavior != null) { - if (immediately) { - crosshairBehavior!.hide(); - } else if (!crosshairBehavior!.shouldAlwaysShow) { - final int hideDelay = crosshairBehavior!.hideDelay > 0 - ? crosshairBehavior!.hideDelay.toInt() - : doubleTapHideDelay; - _crosshairHideTimer?.cancel(); - _crosshairHideTimer = Timer(Duration(milliseconds: hideDelay), () { - _crosshairHideTimer = null; - crosshairBehavior!.hide(); - }); - } - } - } - void raiseTooltip(TooltipInfo info, [PointerDeviceKind kind = PointerDeviceKind.touch]) { if (tooltipBehavior != null && @@ -614,10 +520,6 @@ class RenderBehaviorArea extends RenderBox } } - void _showTrackball(Offset position) { - trackballBehavior?.show(position.dx, position.dy, 'pixel'); - } - void _hideTrackball({int doubleTapHideDelay = 0, bool immediately = false}) { if (trackballBehavior != null) { if (immediately) { @@ -653,19 +555,9 @@ class RenderBehaviorArea extends RenderBox @override void paint(PaintingContext context, Offset offset) { - zoomPanBehavior?._onPaint(context); - crosshairBehavior?._onPaint( - context, - (crosshairBehavior!._position ?? Offset.zero) + offset, - this, - chartThemeData!, - cartesianAxes, - ); - trackballBehavior?._onPaint( - context, - (trackballBehavior!.position ?? Offset.zero) + offset, - chartThemeData!, - ); + zoomPanBehavior?.onPaint(context, offset, chartThemeData!, themeData!); + crosshairBehavior?.onPaint(context, offset, chartThemeData!, themeData!); + trackballBehavior?.onPaint(context, offset, chartThemeData!, themeData!); defaultPaint(context, offset); } @@ -678,6352 +570,7 @@ class RenderBehaviorArea extends RenderBox _crosshairEnabled = false; _trackballEnabled = false; _zoomingEnabled = false; - _performZoomThroughTouch = false; + performZoomThroughTouch = false; super.dispose(); } } - -/// Customizes the zooming options. -/// -/// Customize the various zooming actions such as tap zooming, selection -/// zooming, zoom pinch. In selection zooming, you can long-press and drag to -/// select a range on the chart to be zoomed in and also you can customize the -/// selection zooming rectangle using `selectionRectBorderWidth`, -/// `selectionRectBorderColor` and `selectionRectColor` properties. -/// -/// Pinch zooming can be performed by moving two fingers over the chart. -/// Default mode is [ZoomMode.xy]. Zooming will be stopped after reaching -/// `maximumZoomLevel`. -/// -/// _Note:_ This is only applicable for `SfCartesianChart`. -class ZoomPanBehavior extends ChartBehavior { - /// Creating an argument constructor of ZoomPanBehavior class. - ZoomPanBehavior({ - this.enablePinching = false, - this.enableDoubleTapZooming = false, - this.enablePanning = false, - this.enableSelectionZooming = false, - this.enableMouseWheelZooming = false, - this.zoomMode = ZoomMode.xy, - this.maximumZoomLevel = 0.01, - this.selectionRectBorderWidth = 1, - this.selectionRectBorderColor, - this.selectionRectColor, - }); - - /// Enables or disables the pinch zooming. - /// - /// Pinching can be performed by moving two fingers over the chart. - /// You can zoom the chart through pinch gesture in touch enabled devices. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enablePinching: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enablePinching; - - /// Enables or disables the double tap zooming. - /// - /// Zooming will enable when you tap double time in plot area. - /// After reaching the maximum zoom level, zooming will be stopped. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableDoubleTapZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableDoubleTapZooming; - - /// Enables or disables the panning. - /// - /// Panning can be performed on a zoomed axis. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enablePanning: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enablePanning; - - /// Enables or disables the selection zooming. - /// - /// Selection zooming can be performed by long-press and then dragging. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableSelectionZooming; - - /// Enables or disables the mouseWheelZooming. - /// - /// Mouse wheel zooming can be performed by rolling the mouse wheel up or - /// down. The place where the cursor is hovering gets zoomed in or out - /// according to the mouse wheel rolling up or down. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableMouseWheelZooming: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final bool enableMouseWheelZooming; - - /// By default, both the x and y-axes in the chart can be zoomed. - /// - /// It can be changed by setting value to this property. - /// - /// Defaults to `ZoomMode.xy`. - /// - /// Also refer [ZoomMode]. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// zoomMode: ZoomMode.x - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final ZoomMode zoomMode; - - /// Maximum zoom level. - /// - /// Zooming will be stopped after reached this value and ranges from 0 to 1. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// maximumZoomLevel: 0.8 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final double maximumZoomLevel; - - /// Border width of the selection zooming rectangle. - /// - /// Used to change the stroke width of the selection rectangle. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectBorderWidth: 2 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final double selectionRectBorderWidth; - - /// Border color of the selection zooming rectangle. - /// - /// It used to change the stroke color of the selection rectangle. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectBorderColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final Color? selectionRectBorderColor; - - /// Color of the selection zooming rectangle. - /// - /// It used to change the background color of the selection rectangle. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late ZoomPanBehavior zoomPanBehavior; - /// - /// void initState() { - /// zoomPanBehavior = ZoomPanBehavior( - /// enableSelectionZooming: true, - /// selectionRectColor: Colors.yellow - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// zoomPanBehavior: zoomPanBehavior - /// ); - /// } - /// ``` - final Color? selectionRectColor; - - late bool _isZoomIn, _isZoomOut; - Path? _rectPath; - - bool? _isPinching = false; - Offset? _previousMovedPosition; - Offset? _zoomStartPosition; - List _touchStartPositions = []; - List _touchMovePositions = []; - List _zoomAxes = []; - - /// Holds the value of zooming rect. - Rect _zoomingRect = Rect.zero; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is ZoomPanBehavior && - other.enablePinching == enablePinching && - other.enableDoubleTapZooming == enableDoubleTapZooming && - other.enablePanning == enablePanning && - other.enableSelectionZooming == enableSelectionZooming && - other.enableMouseWheelZooming == enableMouseWheelZooming && - other.zoomMode == zoomMode && - other.maximumZoomLevel == maximumZoomLevel && - other.selectionRectBorderWidth == selectionRectBorderWidth && - other.selectionRectBorderColor == selectionRectBorderColor && - other.selectionRectColor == selectionRectColor; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - enablePinching, - enableDoubleTapZooming, - enablePanning, - enableSelectionZooming, - enableMouseWheelZooming, - zoomMode, - maximumZoomLevel, - selectionRectBorderWidth, - selectionRectBorderColor, - selectionRectColor - ]; - return Object.hashAll(values); - } - - // TODO(VijayakumarM): Avoid exposing it in public. - void _zoom(PointerMoveEvent event) { - Rect? pinchRect; - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - final RenderCartesianAxes? axes = parent.cartesianAxes; - if (axes == null) { - return; - } - final Rect clipRect = parent.paintBounds; - num selectionMin, selectionMax, rangeMin, rangeMax, value, axisTrans; - double currentFactor, currentPosition, maxZoomFactor, currentZoomFactor; - int count = 0; - if (enablePinching == true && _touchStartPositions.length == 2) { - _isPinching = true; - final int pointerID = event.pointer; - bool addPointer = true; - for (int i = 0; i < _touchMovePositions.length; i++) { - if (_touchMovePositions[i].pointer == pointerID) { - addPointer = false; - } - } - if (_touchMovePositions.length < 2 && addPointer) { - _touchMovePositions.add(event); - } - if (_touchMovePositions.length == 2) { - if (_touchMovePositions[0].pointer == event.pointer) { - _touchMovePositions[0] = event; - } - if (_touchMovePositions[1].pointer == event.pointer) { - _touchMovePositions[1] = event; - } - Offset touchStart0, touchEnd0, touchStart1, touchEnd1; - _calculateZoomAxesRange(axes); - final Rect containerRect = Offset.zero & clipRect.size; - touchStart0 = _touchStartPositions[0].position - containerRect.topLeft; - touchEnd0 = _touchMovePositions[0].position - containerRect.topLeft; - touchStart1 = _touchStartPositions[1].position - containerRect.topLeft; - touchEnd1 = _touchMovePositions[1].position - containerRect.topLeft; - final double scaleX = (touchEnd0.dx - touchEnd1.dx).abs() / - (touchStart0.dx - touchStart1.dx).abs(); - final double scaleY = (touchEnd0.dy - touchEnd1.dy).abs() / - (touchStart0.dy - touchStart1.dy).abs(); - final double clipX = ((clipRect.left - touchEnd0.dx) / scaleX) + - min(touchStart0.dx, touchStart1.dx); - final double clipY = ((clipRect.top - touchEnd0.dy) / scaleY) + - min(touchStart0.dy, touchStart1.dy); - pinchRect = Rect.fromLTWH( - clipX, clipY, clipRect.width / scaleX, clipRect.height / scaleY); - } - } - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis && pinchRect != null) { - child.zoomingInProgress = true; - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - if (!child.isVertical) { - value = pinchRect.left - clipRect.left; - axisTrans = clipRect.width / _zoomAxes[count].delta!; - rangeMin = value / axisTrans + _zoomAxes[count].min!; - value = pinchRect.left + pinchRect.width - clipRect.left; - rangeMax = value / axisTrans + _zoomAxes[count].min!; - } else { - value = pinchRect.top - clipRect.top; - axisTrans = clipRect.height / _zoomAxes[count].delta!; - rangeMin = (value * -1 + clipRect.height) / axisTrans + - _zoomAxes[count].min!; - value = pinchRect.top + pinchRect.height - clipRect.top; - rangeMax = (value * -1 + clipRect.height) / axisTrans + - _zoomAxes[count].min!; - } - selectionMin = min(rangeMin, rangeMax); - selectionMax = max(rangeMin, rangeMax); - currentPosition = (selectionMin - _zoomAxes[count].actualMin!) / - _zoomAxes[count].actualDelta!; - currentFactor = - (selectionMax - selectionMin) / _zoomAxes[count].actualDelta!; - child.controller.zoomPosition = - currentPosition < 0 ? 0 : currentPosition; - currentZoomFactor = currentFactor > 1 ? 1 : currentFactor; - maxZoomFactor = maximumZoomLevel; - child.controller.zoomFactor = currentZoomFactor < maxZoomFactor - ? maxZoomFactor - : currentZoomFactor; - } - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - } - count++; - }); - } - - void _pan(Offset currentPosition, Rect plotAreaBound) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - final RenderCartesianAxes? axes = parent.cartesianAxes; - if (axes == null) { - return; - } - double currentZoomPosition; - num currentScale, value; - if (_previousMovedPosition != null) { - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - child.zoomingInProgress = true; - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - currentZoomPosition = child.controller.zoomPosition; - currentScale = - max(1 / _minMax(child.controller.zoomFactor, 0, 1), 1); - if (child.isVertical) { - value = (_previousMovedPosition!.dy - currentPosition.dy) / - plotAreaBound.height / - currentScale; - currentZoomPosition = _minMax( - child.isInversed - ? child.controller.zoomPosition + value - : child.controller.zoomPosition - value, - 0, - 1 - child.controller.zoomFactor); - child.controller.zoomPosition = currentZoomPosition; - } else { - value = (_previousMovedPosition!.dx - currentPosition.dx) / - plotAreaBound.width / - currentScale; - currentZoomPosition = _minMax( - child.isInversed - ? child.controller.zoomPosition - value - : child.controller.zoomPosition + value, - 0, - 1 - child.controller.zoomFactor); - child.controller.zoomPosition = currentZoomPosition; - } - } - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - } - }); - } - _previousMovedPosition = currentPosition; - } - - void _doubleTap(Offset position, Rect plotAreaBounds) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - final RenderCartesianAxes? axes = parent.cartesianAxes; - if (axes == null) { - return; - } - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - child.zoomingInProgress = true; - if (parent.onZoomStart != null) { - _bindZoomEvent(child, parent.onZoomStart!); - } - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - double zoomFactor = child.controller.zoomFactor; - final double cumulative = max( - max(1 / _minMax(child.controller.zoomFactor, 0, 1), 1) + (0.25), - 1); - if (cumulative >= 1) { - double origin = child.isVertical - ? 1 - (position.dy / plotAreaBounds.height) - : position.dx / plotAreaBounds.width; - origin = origin > 1 - ? 1 - : origin < 0 - ? 0 - : origin; - zoomFactor = cumulative == 1 ? 1 : _minMax(1 / cumulative, 0, 1); - final double zoomPosition = (cumulative == 1) - ? 0 - : child.controller.zoomPosition + - ((child.controller.zoomFactor - zoomFactor) * origin); - if (child.controller.zoomPosition != zoomPosition || - child.controller.zoomFactor != zoomFactor) { - zoomFactor = (zoomPosition + zoomFactor) > 1 - ? (1 - zoomPosition) - : zoomFactor; - } - - child.controller.zoomPosition = zoomPosition; - child.controller.zoomFactor = zoomFactor; - } - final double maxZoomFactor = maximumZoomLevel; - if (zoomFactor < maxZoomFactor) { - child.controller.zoomFactor = maxZoomFactor; - child.controller.zoomPosition = 0.0; - zoomFactor = maxZoomFactor; - } - } - if (parent.onZoomEnd != null) { - _bindZoomEvent(child, parent.onZoomEnd!); - } - } - }); - } - - /// Below method is for mouse wheel Zooming. - void _performMouseWheelZooming( - PointerEvent event, double mouseX, double mouseY, Rect plotAreaBounds) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - final RenderCartesianAxes? axes = parent.cartesianAxes; - if (axes == null) { - return; - } - double direction = 0.0; - if (event is PointerScrollEvent) { - direction = (event.scrollDelta.dy / 120) > 0 ? -1 : 1; - } else if (event is PointerPanZoomUpdateEvent) { - direction = event.panDelta.dy == 0 - ? 0 - : (event.panDelta.dy / 120) > 0 - ? 1 - : -1; - } - double origin = 0.5; - double cumulative, zoomFactor, zoomPosition, maxZoomFactor; - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - if (parent.onZoomStart != null) { - _bindZoomEvent(child, parent.onZoomStart!); - } - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - cumulative = max( - max(1 / _minMax(child.controller.zoomFactor, 0, 1), 1) + - (0.25 * direction), - 1); - if (cumulative >= 1) { - origin = child.isVertical - ? 1 - (mouseY / plotAreaBounds.height) - : mouseX / plotAreaBounds.width; - origin = origin > 1 - ? 1 - : origin < 0 - ? 0 - : origin; - zoomFactor = ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)) - .toDouble(); - zoomPosition = (cumulative == 1) - ? 0 - : child.controller.zoomPosition + - ((child.controller.zoomFactor - zoomFactor) * origin); - if (child.controller.zoomPosition != zoomPosition || - child.controller.zoomFactor != zoomFactor) { - zoomFactor = (zoomPosition + zoomFactor) > 1 - ? (1 - zoomPosition) - : zoomFactor; - } - child.controller.zoomPosition = zoomPosition < 0 - ? 0 - : zoomPosition > 1 - ? 1 - : zoomPosition; - child.controller.zoomFactor = zoomFactor < 0 - ? 0 - : zoomFactor > 1 - ? 1 - : zoomFactor; - maxZoomFactor = maximumZoomLevel; - if (zoomFactor < maxZoomFactor) { - child.controller.zoomFactor = maxZoomFactor; - zoomFactor = maxZoomFactor; - } - if (parent.onZoomEnd != null) { - _bindZoomEvent(child, parent.onZoomEnd!); - } - if (child.controller.zoomFactor.toInt() == 1 && - child.controller.zoomPosition.toInt() == 0 && - parent.onZoomReset != null) { - _bindZoomEvent(child, parent.onZoomReset!); - } - } - } - } - }); - } - - /// Below method for drawing selection rectangle. - void _doSelectionZooming(double currentX, double currentY) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - if (_isPinching != true && _zoomStartPosition != null) { - final Offset start = _zoomStartPosition!; - final Rect clipRect = parent.paintBounds; - final Offset startPosition = Offset( - (start.dx < clipRect.left) ? clipRect.left : start.dx, - (start.dy < clipRect.top) ? clipRect.top : start.dy); - final Offset currentMousePosition = Offset( - (currentX > clipRect.right) - ? clipRect.right - : ((currentX < clipRect.left) ? clipRect.left : currentX), - (currentY > clipRect.bottom) - ? clipRect.bottom - : ((currentY < clipRect.top) ? clipRect.top : currentY)); - _rectPath = Path(); - if (zoomMode == ZoomMode.x) { - _rectPath!.moveTo(startPosition.dx, clipRect.top); - _rectPath!.lineTo(startPosition.dx, clipRect.bottom); - _rectPath!.lineTo(currentMousePosition.dx, clipRect.bottom); - _rectPath!.lineTo(currentMousePosition.dx, clipRect.top); - _rectPath!.close(); - } else if (zoomMode == ZoomMode.y) { - _rectPath!.moveTo(clipRect.left, startPosition.dy); - _rectPath!.lineTo(clipRect.left, currentMousePosition.dy); - _rectPath!.lineTo(clipRect.right, currentMousePosition.dy); - _rectPath!.lineTo(clipRect.right, startPosition.dy); - _rectPath!.close(); - } else { - _rectPath!.moveTo(startPosition.dx, startPosition.dy); - _rectPath!.lineTo(startPosition.dx, currentMousePosition.dy); - _rectPath!.lineTo(currentMousePosition.dx, currentMousePosition.dy); - _rectPath!.lineTo(currentMousePosition.dx, startPosition.dy); - _rectPath!.close(); - } - _zoomingRect = _rectPath!.getBounds(); - } - } - - /// Increases the magnification of the plot area. - void zoomIn() { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - _isZoomIn = true; - _isZoomOut = false; - // TODO(YuvarajG): Need to have variable to notify zooming inprogress - // _stateProperties.zoomProgress = true; - bool? needZoom; - RenderChartAxis? child = cartesianAxes.firstChild; - while (child != null) { - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - if (child.controller.zoomFactor <= 1.0 && - child.controller.zoomFactor > 0.0) { - if (child.controller.zoomFactor - 0.1 < 0) { - needZoom = false; - break; - } else { - _updateZoomFactorAndZoomPosition(child); - needZoom = true; - } - } - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - } - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - if (needZoom ?? false) { - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - } - - /// Decreases the magnification of the plot area. - void zoomOut() { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - _isZoomIn = false; - _isZoomOut = true; - // TODO(YuvarajG): Need to have variable to notify zooming inprogress - // _stateProperties.zoomProgress = true; - RenderChartAxis? child = cartesianAxes.firstChild; - while (child != null) { - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - if (child.controller.zoomFactor < 1.0 && - child.controller.zoomFactor > 0.0) { - _updateZoomFactorAndZoomPosition(child); - child.controller.zoomFactor = child.controller.zoomFactor > 1.0 - ? 1.0 - : (child.controller.zoomFactor < 0.0 - ? 0.0 - : child.controller.zoomFactor); - } - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - } - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - - /// Changes the zoom level using zoom factor. - /// - /// Here, you can pass the zoom factor of an axis to magnify the plot - /// area. The value ranges from 0 to 1. - void zoomByFactor(double zoomFactor) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - _isZoomIn = false; - _isZoomOut = true; - // TODO(YuvarajG): Need to have variable to notify zooming inprogress - // _stateProperties.zoomProgress = true; - RenderChartAxis? child = cartesianAxes.firstChild; - if (zoomFactor.clamp(0.0, 1.0) == zoomFactor) { - while (child != null) { - if ((child.isVertical && zoomMode != ZoomMode.x) || - (!child.isVertical && zoomMode != ZoomMode.y)) { - child.controller.zoomFactor = zoomFactor; - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - } - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - } - - /// Zooms the chart for a given rectangle value. - /// - /// Here, you can pass the rectangle with the left, right, top, and bottom - /// values, using which the selection zooming will be performed. - void zoomByRect(Rect rect) { - _drawSelectionZoomRect(rect); - } - - /// Change the zoom level of an appropriate axis. - /// - /// Here, you need to pass axis, zoom factor, zoom position of the zoom level - /// that needs to be modified. - void zoomToSingleAxis( - ChartAxis axis, double zoomPosition, double zoomFactor) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderChartAxis? axisDetails = axis.name != null - ? parent.axisFromName(axis.name) - : parent.axisFromObject(axis); - - if (axisDetails != null) { - axisDetails.controller.zoomFactor = zoomFactor; - axisDetails.controller.zoomPosition = zoomPosition; - } - parent.invalidate(); - } - - /// Pans the plot area for given left, right, top, and bottom directions. - /// - /// To perform this action, the plot area needs to be in zoomed state. - void panToDirection(String direction) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - direction = direction.toLowerCase(); - RenderChartAxis? child = cartesianAxes.firstChild; - while (child != null) { - if (child.isVertical) { - if (direction == 'bottom') { - child.controller.zoomPosition = (child.controller.zoomPosition > 0 && - child.controller.zoomPosition <= 1.0) - ? child.controller.zoomPosition - 0.1 - : child.controller.zoomPosition; - child.controller.zoomPosition = child.controller.zoomPosition < 0.0 - ? 0.0 - : child.controller.zoomPosition; - } - if (direction == 'top') { - child.controller.zoomPosition = (child.controller.zoomPosition >= 0 && - child.controller.zoomPosition < 1) - ? child.controller.zoomPosition + 0.1 - : child.controller.zoomPosition; - child.controller.zoomPosition = child.controller.zoomPosition > 1.0 - ? 1.0 - : child.controller.zoomPosition; - } - } else { - if (direction == 'left') { - child.controller.zoomPosition = (child.controller.zoomPosition > 0 && - child.controller.zoomPosition <= 1.0) - ? child.controller.zoomPosition - 0.1 - : child.controller.zoomPosition; - child.controller.zoomPosition = child.controller.zoomPosition < 0.0 - ? 0.0 - : child.controller.zoomPosition; - } - if (direction == 'right') { - child.controller.zoomPosition = (child.controller.zoomPosition >= 0 && - child.controller.zoomPosition < 1) - ? child.controller.zoomPosition + 0.1 - : child.controller.zoomPosition; - child.controller.zoomPosition = child.controller.zoomPosition > 1.0 - ? 1.0 - : child.controller.zoomPosition; - } - } - if (parent.onZooming != null) { - _bindZoomEvent(child, parent.onZooming!); - } - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - parent.invalidate(); - } - - /// Returns the plot area back to its original position after zooming. - void reset() { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - RenderChartAxis? child = cartesianAxes.firstChild; - while (child != null) { - child.controller.zoomFactor = 1.0; - child.controller.zoomPosition = 0.0; - if (parent.onZoomReset != null) { - _bindZoomEvent(child, parent.onZoomReset!); - } - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - parent.invalidate(); - } - - ZoomPanArgs _bindZoomEvent( - RenderChartAxis axis, ChartZoomingCallback zoomEventType) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - final ZoomPanArgs zoomPanArgs = ZoomPanArgs( - axis, - axis.controller.previousZoomPosition, - axis.controller.previousZoomFactor); - zoomPanArgs.currentZoomFactor = axis.controller.zoomFactor; - zoomPanArgs.currentZoomPosition = axis.controller.zoomPosition; - if (parent == null) { - return zoomPanArgs; - } - zoomEventType == parent.onZoomStart - ? parent.onZoomStart!(zoomPanArgs) - : zoomEventType == parent.onZoomEnd - ? parent.onZoomEnd!(zoomPanArgs) - : zoomEventType == parent.onZooming - ? parent.onZooming!(zoomPanArgs) - : parent.onZoomReset!(zoomPanArgs); - return zoomPanArgs; - } - - /// Below method for zooming selected portion. - void _drawSelectionZoomRect(Rect zoomRect) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - parent.hideInteractiveTooltip(); - final RenderCartesianAxes? axes = parent.cartesianAxes; - if (axes == null) { - return; - } - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - if (parent.onZoomStart != null) { - _bindZoomEvent(child, parent.onZoomStart!); - } - if (child.isVertical) { - if (zoomMode != ZoomMode.x) { - child.controller.zoomPosition += (1 - - ((zoomRect.height + - (zoomRect.top - child.paintBounds.top)) / - (child.paintBounds.height)) - .abs()) * - child.controller.zoomFactor; - child.controller.zoomFactor *= - zoomRect.height / child.paintBounds.height; - - child.controller.zoomFactor = - child.controller.zoomFactor >= maximumZoomLevel - ? child.controller.zoomFactor - : maximumZoomLevel; - } - } else { - if (zoomMode != ZoomMode.y) { - child.controller.zoomPosition += - ((zoomRect.left - child.paintBounds.left) / - (child.paintBounds.width)) - .abs() * - child.controller.zoomFactor; - child.controller.zoomFactor *= - zoomRect.width / child.paintBounds.width; - child.controller.zoomFactor = - child.controller.zoomFactor >= maximumZoomLevel - ? child.controller.zoomFactor - : maximumZoomLevel; - } - } - if (parent.onZoomEnd != null) { - _bindZoomEvent(child, parent.onZoomEnd!); - } - } - }); - zoomRect = Rect.zero; - _rectPath = Path(); - } - - double _minMax(double value, double min, double max) { - return value > max ? max : (value < min ? min : value); - } - - void _onPaint(PaintingContext context) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - if (_zoomingRect != Rect.zero && _rectPath != null) { - Color? fillColor = selectionRectColor; - if (fillColor != null && - fillColor != Colors.transparent && - fillColor.opacity == 1) { - fillColor = fillColor.withOpacity(0.3); - } - final Paint fillPaint = Paint() - ..color = fillColor ?? cartesianAxes.chartThemeData.selectionRectColor - ..style = PaintingStyle.fill; - context.canvas.drawRect(_zoomingRect, fillPaint); - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..color = selectionRectBorderColor ?? - cartesianAxes.chartThemeData.selectionRectBorderColor - ..strokeWidth = selectionRectBorderWidth - ..style = PaintingStyle.stroke; - - if (strokePaint.color != Colors.transparent && - strokePaint.strokeWidth > 0) { - final List dashArray = [5, 5]; - drawDashes(context.canvas, dashArray, strokePaint, path: _rectPath); - } - - final Offset plotAreaOffset = - (parent.parentData! as BoxParentData).offset; - //Selection zooming tooltip rendering - _drawTooltipConnector( - cartesianAxes, - _zoomingRect.topLeft, - _zoomingRect.bottomRight, - context.canvas, - parent.paintBounds, - plotAreaOffset); - } - } - - void _calculateZoomAxesRange(RenderCartesianAxes axes) { - ZoomAxisRange range; - axes.visitChildren((RenderObject child) { - range = ZoomAxisRange(); - if (child is RenderChartAxis) { - if (child.actualRange != null) { - range.actualMin = child.actualRange!.minimum.toDouble(); - range.actualDelta = child.actualRange!.delta.toDouble(); - } - range.min = child.visibleRange!.minimum.toDouble(); - range.delta = child.visibleRange!.delta.toDouble(); - _zoomAxes.add(range); - } - }); - } - - /// Returns the tooltip label on zooming. - String _tooltipValue( - Offset position, RenderChartAxis axis, Rect plotAreaBounds) { - final num value = axis.isVertical - ? axis.pixelToPoint(axis.paintBounds, position.dx, position.dy) - : axis.pixelToPoint(axis.paintBounds, position.dx - plotAreaBounds.left, - position.dy - plotAreaBounds.top); - - dynamic result = _interactiveTooltipLabel(value, axis); - if (axis.interactiveTooltip.format != null) { - final String stringValue = - axis.interactiveTooltip.format!.replaceAll('{value}', result); - result = stringValue; - } - return result.toString(); - } - - /// Validate the rect by comparing small and large rect. - Rect _validateRect(Rect largeRect, Rect smallRect, String axisPosition) => - Rect.fromLTRB( - axisPosition == 'left' - ? (smallRect.left - (largeRect.width - smallRect.width)) - : smallRect.left, - smallRect.top, - axisPosition == 'right' - ? (smallRect.right + (largeRect.width - smallRect.width)) - : smallRect.right, - smallRect.bottom); - - /// Calculate the interactive tooltip rect, based on the zoomed axis position. - Rect _calculateRect(RenderChartAxis axis, Offset position, Size labelSize) { - const double paddingForRect = 10; - final Rect axisBound = - (axis.parentData! as BoxParentData).offset & axis.size; - final double arrowLength = axis.interactiveTooltip.arrowLength; - double left, top; - final double width = labelSize.width + paddingForRect; - final double height = labelSize.height + paddingForRect; - - if (axis.isVertical) { - top = position.dy - height / 2; - if (axis.opposedPosition) { - left = axisBound.left + arrowLength; - } else { - left = axisBound.left - width - arrowLength; - } - } else { - left = position.dx - width / 2; - if (axis.opposedPosition) { - top = axisBound.top - height - arrowLength; - } else { - top = axisBound.top + arrowLength; - } - } - return Rect.fromLTWH(left, top, width, height); - } - - /// To draw tooltip connector. - void _drawTooltipConnector( - RenderCartesianAxes axes, - Offset startPosition, - Offset endPosition, - Canvas canvas, - Rect plotAreaBounds, - Offset plotAreaOffset) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - RRect? startTooltipRect, endTooltipRect; - String startValue, endValue; - Size startLabelSize, endLabelSize; - Rect startLabelRect, endLabelRect; - TextStyle textStyle = - parent!.chartThemeData!.selectionZoomingTooltipTextStyle!; - final Paint labelFillPaint = Paint() - ..color = axes.chartThemeData.crosshairBackgroundColor - ..isAntiAlias = true; - - final Paint labelStrokePaint = Paint() - ..color = axes.chartThemeData.crosshairBackgroundColor - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - if (child.interactiveTooltip.enable) { - textStyle = textStyle.merge(child.interactiveTooltip.textStyle); - labelFillPaint.color = child.interactiveTooltip.color ?? - axes.chartThemeData.crosshairBackgroundColor; - labelStrokePaint.color = child.interactiveTooltip.borderColor ?? - axes.chartThemeData.crosshairBackgroundColor; - labelStrokePaint.strokeWidth = child.interactiveTooltip.borderWidth; - final Paint connectorLinePaint = Paint() - ..color = child.interactiveTooltip.connectorLineColor ?? - axes.chartThemeData.selectionTooltipConnectorLineColor - ..strokeWidth = child.interactiveTooltip.connectorLineWidth - ..style = PaintingStyle.stroke; - - final Path startLabelPath = Path(); - final Path endLabelPath = Path(); - startValue = _tooltipValue(startPosition, child, plotAreaBounds); - endValue = _tooltipValue(endPosition, child, plotAreaBounds); - - if (startValue.isNotEmpty && endValue.isNotEmpty) { - startLabelSize = measureText(startValue, textStyle); - endLabelSize = measureText(endValue, textStyle); - startLabelRect = - _calculateRect(child, startPosition, startLabelSize); - endLabelRect = _calculateRect(child, endPosition, endLabelSize); - if (child.isVertical && - startLabelRect.width != endLabelRect.width) { - final String axisPosition = - child.opposedPosition ? 'right' : 'left'; - (startLabelRect.width > endLabelRect.width) - ? endLabelRect = - _validateRect(startLabelRect, endLabelRect, axisPosition) - : startLabelRect = - _validateRect(endLabelRect, startLabelRect, axisPosition); - } - startTooltipRect = _drawTooltip( - canvas, - labelFillPaint, - labelStrokePaint, - startLabelPath, - startPosition, - startLabelRect, - startTooltipRect, - startValue, - startLabelSize, - plotAreaBounds, - textStyle, - child, - plotAreaOffset); - endTooltipRect = _drawTooltip( - canvas, - labelFillPaint, - labelStrokePaint, - endLabelPath, - endPosition, - endLabelRect, - endTooltipRect, - endValue, - endLabelSize, - plotAreaBounds, - textStyle, - child, - plotAreaOffset); - _drawConnector(canvas, connectorLinePaint, startTooltipRect!, - endTooltipRect!, startPosition, endPosition, child); - } - } - } - }); - } - - /// To draw connectors. - void _drawConnector( - Canvas canvas, - Paint connectorLinePaint, - RRect startTooltipRect, - RRect endTooltipRect, - Offset startPosition, - Offset endPosition, - RenderChartAxis axis) { - final InteractiveTooltip tooltip = axis.interactiveTooltip; - if (!axis.isVertical && !axis.opposedPosition) { - startPosition = - Offset(startPosition.dx, startTooltipRect.top - tooltip.arrowLength); - endPosition = - Offset(endPosition.dx, endTooltipRect.top - tooltip.arrowLength); - } else if (!axis.isVertical && axis.opposedPosition) { - startPosition = Offset( - startPosition.dx, startTooltipRect.bottom + tooltip.arrowLength); - endPosition = - Offset(endPosition.dx, endTooltipRect.bottom + tooltip.arrowLength); - } else if (axis.isVertical && !axis.opposedPosition) { - startPosition = Offset( - startTooltipRect.right + tooltip.arrowLength, startPosition.dy); - endPosition = - Offset(endTooltipRect.right + tooltip.arrowLength, endPosition.dy); - } else { - startPosition = - Offset(startTooltipRect.left - tooltip.arrowLength, startPosition.dy); - endPosition = - Offset(endTooltipRect.left - tooltip.arrowLength, endPosition.dy); - } - drawDashedPath(canvas, connectorLinePaint, startPosition, endPosition, - tooltip.connectorLineDashArray); - } - - /// To draw tooltip. - RRect _drawTooltip( - Canvas canvas, - Paint fillPaint, - Paint strokePaint, - Path path, - Offset position, - Rect labelRect, - RRect? rect, - String value, - Size labelSize, - Rect plotAreaBound, - TextStyle textStyle, - RenderChartAxis axis, - Offset plotAreaOffset) { - final Offset parentDataOffset = (axis.parentData! as BoxParentData).offset; - final Offset axisOffset = - parentDataOffset.translate(-plotAreaOffset.dx, -plotAreaOffset.dy); - final Rect axisRect = axisOffset & axis.size; - labelRect = _validateRectBounds(labelRect, axisRect); - labelRect = axis.isVertical - ? _validateRectYPosition(labelRect, plotAreaBound) - : _validateRectXPosition(labelRect, plotAreaBound); - path.reset(); - rect = RRect.fromRectAndRadius( - labelRect, Radius.circular(axis.interactiveTooltip.borderRadius)); - path.addRRect(rect); - _calculateNeckPositions( - canvas, fillPaint, strokePaint, path, position, rect, axis); - drawText( - canvas, - value, - Offset((rect.left + rect.width / 2) - labelSize.width / 2, - (rect.top + rect.height / 2) - labelSize.height / 2), - textStyle, - ); - return rect; - } - - /// To calculate tooltip neck positions. - void _calculateNeckPositions( - Canvas canvas, - Paint fillPaint, - Paint strokePaint, - Path path, - Offset position, - RRect rect, - RenderChartAxis axis) { - final InteractiveTooltip tooltip = axis.interactiveTooltip; - double x1, x2, x3, x4, y1, y2, y3, y4; - if (!axis.isVertical && !axis.opposedPosition) { - x1 = position.dx; - y1 = rect.top - tooltip.arrowLength; - x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; - y2 = rect.top; - x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; - y3 = rect.top; - x4 = position.dx; - y4 = rect.top - tooltip.arrowLength; - } else if (!axis.isVertical && axis.opposedPosition) { - x1 = position.dx; - y1 = rect.bottom + tooltip.arrowLength; - x2 = (rect.right - rect.width / 2) + tooltip.arrowWidth; - y2 = rect.bottom; - x3 = (rect.left + rect.width / 2) - tooltip.arrowWidth; - y3 = rect.bottom; - x4 = position.dx; - y4 = rect.bottom + tooltip.arrowLength; - } else if (axis.isVertical && !axis.opposedPosition) { - x1 = rect.right; - y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; - x2 = rect.right; - y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; - x3 = rect.right + tooltip.arrowLength; - y3 = position.dy; - x4 = rect.right + tooltip.arrowLength; - y4 = position.dy; - } else { - x1 = rect.left; - y1 = rect.top + rect.height / 2 - tooltip.arrowWidth; - x2 = rect.left; - y2 = rect.bottom - rect.height / 2 + tooltip.arrowWidth; - x3 = rect.left - tooltip.arrowLength; - y3 = position.dy; - x4 = rect.left - tooltip.arrowLength; - y4 = position.dy; - } - _drawTooltipArrowhead( - canvas, path, fillPaint, strokePaint, x1, y1, x2, y2, x3, y3, x4, y4); - } - - /// Below method is for zoomIn and zoomOut public methods. - void _updateZoomFactorAndZoomPosition(RenderChartAxis axis) { - final Rect axisClipRect = axis.paintBounds; - double? zoomFactor, zoomPosition; - final num direction = _isZoomIn - ? 1 - : _isZoomOut - ? -1 - : 1; - final num cumulative = max( - max(1 / _minMax(axis.controller.zoomFactor, 0, 1), 1) + - (0.1 * direction), - 1); - if (cumulative >= 1) { - num origin = axis.isVertical - ? 1 - - ((axisClipRect.top + axisClipRect.height / 2) / - axisClipRect.height) - : (axisClipRect.left + axisClipRect.width / 2) / axisClipRect.width; - origin = origin > 1 - ? 1 - : origin < 0 - ? 0 - : origin; - zoomFactor = - ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)).toDouble(); - zoomPosition = (cumulative == 1) - ? 0 - : axis.controller.zoomPosition + - ((axis.controller.zoomFactor - zoomFactor) * origin); - if (axis.controller.zoomPosition != zoomPosition || - axis.controller.zoomFactor != zoomFactor) { - zoomFactor = - (zoomPosition + zoomFactor) > 1 ? (1 - zoomPosition) : zoomFactor; - } - - axis.controller.zoomPosition = zoomPosition; - axis.controller.zoomFactor = zoomFactor; - } - } - - void _startPinchZooming(PointerEvent event) { - if (_touchStartPositions.length < 2) { - _touchStartPositions.add(event); - } - - if (_touchStartPositions.length == 2) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null && - parent.onZoomStart != null && - parent.cartesianAxes != null) { - parent.hideInteractiveTooltip(); - final RenderCartesianAxes axes = parent.cartesianAxes!; - - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - _bindZoomEvent(child, parent.onZoomStart!); - } - }); - } - } - } - - void _endPinchZooming(PointerUpEvent event) { - if (_touchStartPositions.length == 2 && _touchMovePositions.length == 2) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null && parent.cartesianAxes != null) { - final RenderCartesianAxes axes = parent.cartesianAxes!; - - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - if (parent.onZoomEnd != null) { - _bindZoomEvent(child, parent.onZoomEnd!); - } - child.zoomingInProgress = false; - } - }); - } - } - - _zoomAxes = []; - _touchMovePositions = []; - _touchStartPositions = []; - _isPinching = false; - } - - void _startPanning() { - _previousMovedPosition = null; - } - - void _endPanning() { - _previousMovedPosition = null; - - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null && parent.cartesianAxes != null) { - final RenderCartesianAxes axes = parent.cartesianAxes!; - - axes.visitChildren((RenderObject child) { - if (child is RenderChartAxis) { - child.zoomingInProgress = false; - } - }); - } - } - - void _longPressStart(Offset position) { - if (_zoomStartPosition != position) { - _zoomStartPosition = position; - } - } - - void _longPressEnd() { - if (_zoomStartPosition != null && _zoomingRect.width != 0) { - _drawSelectionZoomRect(_zoomingRect); - } - _zoomStartPosition = null; - _zoomingRect = Rect.zero; - } -} - -/// This method will validate whether the tooltip exceeds the screen or not. -Rect _validateRectBounds(Rect tooltipRect, Rect boundary) { - Rect validatedRect = tooltipRect; - double difference = 0; - - /// Padding between the corners. - const double padding = 0.5; - - // Move the tooltip if it's outside of the boundary. - if (tooltipRect.left < boundary.left) { - difference = (boundary.left - tooltipRect.left) + padding; - validatedRect = validatedRect.translate(difference, 0); - } - if (tooltipRect.right > boundary.right) { - difference = (tooltipRect.right - boundary.right) + padding; - validatedRect = validatedRect.translate(-difference, 0); - } - if (tooltipRect.top < boundary.top) { - difference = (boundary.top - tooltipRect.top) + padding; - validatedRect = validatedRect.translate(0, difference); - } - - if (tooltipRect.bottom > boundary.bottom) { - difference = (tooltipRect.bottom - boundary.bottom) + padding; - validatedRect = validatedRect.translate(0, -difference); - } - return validatedRect; -} - -/// Gets the x position of validated rect. -Rect _validateRectYPosition(Rect labelRect, Rect axisClipRect) { - Rect validatedRect = labelRect; - if (labelRect.bottom >= axisClipRect.bottom) { - validatedRect = Rect.fromLTRB( - labelRect.left, - labelRect.top - (labelRect.bottom - axisClipRect.bottom), - labelRect.right, - axisClipRect.bottom); - } else if (labelRect.top <= axisClipRect.top) { - validatedRect = Rect.fromLTRB(labelRect.left, axisClipRect.top, - labelRect.right, labelRect.bottom + (axisClipRect.top - labelRect.top)); - } - return validatedRect; -} - -/// Gets the x position of validated rect. -Rect _validateRectXPosition(Rect labelRect, Rect axisClipRect) { - Rect validatedRect = labelRect; - if (labelRect.right >= axisClipRect.right) { - validatedRect = Rect.fromLTRB( - labelRect.left - (labelRect.right - axisClipRect.right), - labelRect.top, - axisClipRect.right, - labelRect.bottom); - } else if (labelRect.left <= axisClipRect.left) { - validatedRect = Rect.fromLTRB( - axisClipRect.left, - labelRect.top, - labelRect.right + (axisClipRect.left - labelRect.left), - labelRect.bottom); - } - return validatedRect; -} - -/// Draw tooltip arrow head. -void _drawTooltipArrowhead( - Canvas canvas, - Path backgroundPath, - Paint fillPaint, - Paint strokePaint, - double x1, - double y1, - double x2, - double y2, - double x3, - double y3, - double x4, - double y4) { - backgroundPath.moveTo(x1, y1); - backgroundPath.lineTo(x2, y2); - backgroundPath.lineTo(x3, y3); - backgroundPath.lineTo(x4, y4); - backgroundPath.lineTo(x1, y1); - fillPaint.isAntiAlias = true; - canvas.drawPath(backgroundPath, strokePaint); - canvas.drawPath(backgroundPath, fillPaint); -} - -/// To get interactive tooltip label. -dynamic _interactiveTooltipLabel(dynamic value, RenderChartAxis axis) { - if (axis.visibleLabels.isEmpty) { - return ''; - } - - final int labelsLength = axis.visibleLabels.length; - if (axis is RenderCategoryAxis) { - value = value < 0 ? 0 : value; - value = axis.labels[(value.round() >= axis.labels.length - ? (value.round() > axis.labels.length - ? axis.labels.length - 1 - : value - 1) - : value.round()) - .round()]; - } else if (axis is RenderDateTimeCategoryAxis) { - value = value < 0 ? 0 : value; - value = axis.labels[(value.round() >= axis.labels.length - ? (value.round() > axis.labels.length - ? axis.labels.length - 1 - : value - 1) - : value.round()) - .round()]; - } else if (axis is RenderDateTimeAxis) { - final num interval = axis.visibleRange!.minimum.ceil(); - final num previousInterval = (axis.visibleLabels.isNotEmpty) - ? axis.visibleLabels[labelsLength - 1].value - : interval; - final DateFormat dateFormat = axis.dateFormat ?? - _dateTimeLabelFormat(axis, interval.toInt(), previousInterval.toInt()); - value = - dateFormat.format(DateTime.fromMillisecondsSinceEpoch(value.toInt())); - } else { - value = axis is RenderLogarithmicAxis ? pow(10, value) : value; - value = _labelValue(value, axis, axis.interactiveTooltip.decimalPlaces); - } - return value; -} - -/// To get the label format of the date-time axis. -DateFormat _dateTimeLabelFormat(RenderChartAxis axis, - [int? interval, int? prevInterval]) { - DateFormat? format; - final bool notDoubleInterval = - (axis.interval != null && axis.interval! % 1 == 0) || - axis.interval == null; - DateTimeIntervalType? actualIntervalType; - num? minimum; - if (axis is RenderDateTimeAxis) { - actualIntervalType = axis.visibleIntervalType; - minimum = axis.visibleRange!.minimum; - } else if (axis is RenderDateTimeCategoryAxis) { - minimum = axis.visibleRange!.minimum; - actualIntervalType = axis.visibleIntervalType; - } - switch (actualIntervalType) { - case DateTimeIntervalType.years: - format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.months: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - - break; - case DateTimeIntervalType.days: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - break; - case DateTimeIntervalType.hours: - format = DateFormat.j(); - break; - case DateTimeIntervalType.minutes: - format = DateFormat.Hm(); - break; - case DateTimeIntervalType.seconds: - format = DateFormat.ms(); - break; - case DateTimeIntervalType.milliseconds: - final DateFormat dateFormat = DateFormat('ss.SSS'); - format = dateFormat; - break; - case DateTimeIntervalType.auto: - break; - // ignore: no_default_cases - default: - break; - } - return format!; -} - -/// Gets the the actual label value for tooltip and data label etc. -String _labelValue(dynamic value, dynamic axis, [int? showDigits]) { - if (value.toString().split('.').length > 1) { - final String str = value.toString(); - final List list = str.split('.'); - value = double.parse(value.toStringAsFixed(showDigits ?? 3)); - value = (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000' || - list[1] == '0000000') - ? value.round() - : value; - } - final dynamic text = axis is NumericAxis && axis.numberFormat != null - ? axis.numberFormat!.format(value) - : value; - return ((axis.labelFormat != null && axis.labelFormat != '') - ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString()) - : text.toString()) as String; -} - -/// Calculate the dateTime format. -DateFormat? _dateTimeFormat(DateTimeIntervalType? actualIntervalType, - int? interval, int? prevInterval) { - final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); - final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); - DateFormat? format; - final bool isIntervalDecimal = interval % 1 == 0; - if (actualIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; -} - -/// Returns the first label format for date time values. -DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { - DateFormat? format; - - if (actualIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (actualIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - - return format; -} - -/// Represents the zoom axis range class. -class ZoomAxisRange { - /// Creates an instance of zoom axis range class. - ZoomAxisRange({this.actualMin, this.actualDelta, this.min, this.delta}); - - /// Holds the value of actual minimum, actual delta, minimum and delta value. - double? actualMin, actualDelta, min, delta; -} - -/// Customizes the trackball. -/// -/// Trackball feature displays the tooltip for the data points that are closer -/// to the point where you touch on the chart area. -/// This feature can be enabled using enable property of [TrackballBehavior]. -/// -/// Provides options to customize the [activationMode], [tooltipDisplayMode], -/// [lineType] and [tooltipSettings]. -class TrackballBehavior extends ChartBehavior { - /// Creating an argument constructor of TrackballBehavior class. - TrackballBehavior({ - this.activationMode = ActivationMode.longPress, - this.lineType = TrackballLineType.vertical, - this.tooltipDisplayMode = TrackballDisplayMode.floatAllPoints, - this.tooltipAlignment = ChartAlignment.center, - this.tooltipSettings = const InteractiveTooltip(), - this.markerSettings, - this.lineDashArray, - this.enable = false, - this.lineColor, - this.lineWidth = 1, - this.shouldAlwaysShow = false, - this.builder, - this.hideDelay = 0, - }); - - /// Toggles the visibility of the trackball. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior(enable: true); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final bool enable; - - /// Width of the track line. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineWidth: 5 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final double lineWidth; - - /// Color of the track line. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final Color? lineColor; - - /// Dashes of the track line. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineDashArray: [10,10] - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final List? lineDashArray; - - /// Gesture for activating the trackball. - /// - /// Trackball can be activated in tap, double tap and long press. - /// - /// Defaults to `ActivationMode.longPress`. - /// - /// Also refer [ActivationMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// activationMode: ActivationMode.doubleTap - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ActivationMode activationMode; - - /// Alignment of the trackball tooltip. - /// - /// The trackball tooltip can be aligned at the top, bottom, and center - /// position of the chart. - /// - /// _Note:_ This is applicable only when the `tooltipDisplayMode` property - /// is set to `TrackballDisplayMode.groupAllPoints`. - /// - /// Defaults to `ChartAlignment.center` - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints, - /// tooltipAlignment: ChartAlignment.far - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ChartAlignment tooltipAlignment; - - /// Type of trackball line. By default, vertical line will be displayed. - /// - /// You can change this by specifying values to this property. - /// - /// Defaults to `TrackballLineType.vertical`. - /// - /// Also refer [TrackballLineType] - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// lineType: TrackballLineType.vertical - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final TrackballLineType lineType; - - /// Display mode of tooltip. - /// - /// By default, tooltip of all the series under the current point index value - /// will be shown. - /// - /// Defaults to `TrackballDisplayMode.floatAllPoints`. - /// - /// Also refer [TrackballDisplayMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// tooltipDisplayMode: TrackballDisplayMode.groupAllPoints - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final TrackballDisplayMode tooltipDisplayMode; - - /// Shows or hides the trackball. - /// - /// By default, the trackball will be hidden on touch. To avoid this, - /// set this property to true. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// shouldAlwaysShow: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final bool shouldAlwaysShow; - - /// Customizes the trackball tooltip. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// canShowMarker: false - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final InteractiveTooltip tooltipSettings; - - /// Giving disappear delay for trackball. - /// - /// Defaults to `0`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// hideDelay: 2000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior, - /// ); - /// } - /// ``` - final double hideDelay; - - /// Builder of the trackball tooltip. - /// - /// Add any custom widget as the trackball template. - /// - /// If the trackball display mode is `groupAllPoints` or `nearestPoint` - /// it will called once and if it is - /// `floatAllPoints`, it will be called for each point. - /// - /// Defaults to `null`. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// builder: (BuildContext context, TrackballDetails trackballDetails) { - /// return Container( - /// width: 70, - /// decoration: - /// const BoxDecoration(color: Color.fromRGBO(66, 244, 164, 1)), - /// child: Text('${trackballDetails.point?.cumulative}') - /// ); - /// } - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - /// ``` - final ChartTrackballBuilder? builder; - - /// Hold crosshair target position. - Offset? get position => _position; - Offset? _position; - - late ChartTrackballInfo? tooltipInfo; - - bool _isTransposed = false; - - /// Represents the value of border radius. - late double _borderRadius; - - /// Represents the value of background path. - final Path _backgroundPath = Path(); - - /// Specifies whether the divider is needed or not. - bool _divider = true; - - /// Specifies whether the trackball header text is to be rendered or not. - bool _headerText = false; - - /// Specifies the value for formatting x value. - bool _xFormat = false; - - /// Specifies whether the labelFormat contains colon or not. - bool _isColon = true; - - /// Specifies the text style for label. - late TextStyle _labelStyle; - - /// Specifies the list of string values for the trackball. - List _stringValue = []; - - List chartPointInfo = []; - - /// Specifies the list of marker shaper paths. - final List _markerShapes = []; - - List _tooltipTop = []; - - /// Specifies the list of tooltip bottom values. - List _tooltipBottom = []; - - final List xAxesInfo = []; - - final List yAxesInfo = []; - - bool _isGroupMode = false; - - late List visiblePoints; - - TooltipPositions? tooltipPosition; - - num _padding = 5; - - late num _tooltipPadding; - - late SfChartThemeData? _chartTheme; - - /// Represents the value of pointer length. - late double _pointerLength; - - /// Represents the value of pointer width. - late double _pointerWidth; - - /// Specifies the value of nose point y value. - double _nosePointY = 0; - - /// Specifies the value of nose point x value. - double _nosePointX = 0; - - /// Specifies the value of total width. - double _totalWidth = 0; - - /// Represents the value of x value. - late double? _x; - - /// Represents the value of y value. - late double? _y; - - /// Represents the value of x position. - late double? _xPos; - - /// Represents the value of y position. - late double? _yPos; - - /// Represents the value of isTop. - bool _isTop = false; - - /// Represents the value of is left. - bool _isLeft = false; - - /// Represents the value of is right. - bool _isRight = false; - - /// Represents the boundary rect for trackball. - Rect boundaryRect = Rect.zero; - - /// Specifies whether the series is rect type or not. - bool _isRectSeries = false; - - /// Represents the value of last marker result height. - double _lastMarkerResultHeight = 0.0; - - /// Represents the rect value of label. - late Rect _labelRect; - - late double _markerSize, _markerPadding; - - bool _isRangeSeries = false; - - /// Specifies whether it is box series - bool _isBoxSeries = false; - - /// Specifies whether the trackball has template - final bool _isTrackballTemplate = false; - - /// Represents the value for canResetPath for trackball. - bool _canResetPath = true; - - List _visibleLocation = []; - - bool _isRtl = false; - - dart_ui.Image? _image; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TrackballBehavior && - other.activationMode == activationMode && - other.lineType == lineType && - other.tooltipDisplayMode == tooltipDisplayMode && - other.tooltipAlignment == tooltipAlignment && - other.tooltipSettings == tooltipSettings && - other.lineDashArray == lineDashArray && - other.markerSettings == markerSettings && - other.enable == enable && - other.lineColor == lineColor && - other.lineWidth == lineWidth && - other.shouldAlwaysShow == shouldAlwaysShow && - other.builder == builder && - other.hideDelay == hideDelay; - } - - @override - int get hashCode { - final List values = [ - activationMode, - lineType, - tooltipDisplayMode, - tooltipAlignment, - tooltipSettings, - markerSettings, - lineDashArray, - enable, - lineColor, - lineWidth, - shouldAlwaysShow, - builder, - hideDelay - ]; - return Object.hashAll(values); - } - - /// Options to customize the markers that are displayed when trackball is - /// enabled. - /// - /// Trackball markers are used to provide information about the exact point - /// location, when the trackball is visible. You can add a shape to adorn each - /// data point. Trackball markers can be enabled by using the - /// `markerVisibility` property in [TrackballMarkerSettings]. - /// - /// Provides the options like color, border width, border color and shape of - /// the marker to customize the appearance. - final TrackballMarkerSettings? markerSettings; - - /// Displays the trackball at the specified x and y-positions. - /// - /// *x and y - x & y pixels/values at which the trackball needs to be shown. - /// - /// coordinateUnit - specify the type of x and y values given. - /// - /// `pixel` or `point` for logical pixel and chart data point respectively. - /// - /// Defaults to `point`. - void show(dynamic x, double y, [String coordinateUnit = 'point']) { - assert(x != null); - assert(!y.isNaN); - if (coordinateUnit == 'point') { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null) { - _position = rawValueToPixelPoint( - x, y, parent.xAxis, parent.yAxis, parent.isTransposed); - } - } else if (coordinateUnit == 'pixel') { - _position = Offset(x, y); - } - if (builder == null) { - _generateAllPoints(position!); - (parentBox as RenderBehaviorArea?)?.invalidate(); - } else { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - if (_position == null) { - return; - } - _generateAllPoints(_position!); - List points = []; - List currentPointIndices = []; - List visibleSeriesIndices = []; - List visibleSeriesList = []; - TrackballGroupingModeInfo groupingModeInfo; - - for (int index = 0; index < chartPointInfo.length; index++) { - final CartesianChartPoint dataPoint = - chartPointInfo[index].chartDataPoint!; - if (tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { - points.add(dataPoint); - currentPointIndices.add(chartPointInfo[index].dataPointIndex!); - visibleSeriesIndices.add(chartPointInfo[index].seriesIndex); - visibleSeriesList.add(chartPointInfo[index].series!); - } - } - groupingModeInfo = TrackballGroupingModeInfo( - points, currentPointIndices, visibleSeriesIndices, visibleSeriesList); - final List details = []; - for (int i = 0; i < chartPointInfo.length; i++) { - if (tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { - details - .add(TrackballDetails(null, null, null, null, groupingModeInfo)); - break; - } else { - details.add(TrackballDetails( - chartPointInfo[i].chartDataPoint, - chartPointInfo[i].series!, - chartPointInfo[i].dataPointIndex, - chartPointInfo[i].seriesIndex, - )); - } - } - parent.trackballBuilder!(details); - points = []; - currentPointIndices = []; - visibleSeriesIndices = []; - visibleSeriesList = []; - } - } - - /// Displays the trackball at the specified point index. - /// - /// * pointIndex - index of the point for which the trackball must be shown - void showByIndex(int pointIndex) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - final CartesianSeriesRenderer? child = - parent!.plotArea!.firstChild! as CartesianSeriesRenderer?; - if (child != null) { - final List visibleIndexes = child.visibleIndexes; - if (visibleIndexes.first <= pointIndex && - pointIndex <= visibleIndexes.last) { - show(child.xRawValues[pointIndex], 0); - } - } - } - - /// Hides the trackball if it is displayed. - void hide() { - _position = null; - if (builder != null) { - (parentBox as RenderBehaviorArea?) - ?.trackballBuilder!([]); - chartPointInfo.clear(); - } - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - - void _onPaint( - PaintingContext context, Offset offset, SfChartThemeData chartThemeData) { - if (position == null) { - return; - } - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - - if (parent == null) { - return; - } - _canResetPath = false; - _isRtl = parent.textDirection == TextDirection.rtl; - _chartTheme = chartThemeData; - _borderRadius = tooltipSettings.borderRadius; - _pointerLength = tooltipSettings.arrowLength; - _pointerWidth = tooltipSettings.arrowWidth; - _isGroupMode = tooltipDisplayMode == TrackballDisplayMode.groupAllPoints; - _isLeft = false; - _isRight = false; - double height = 0, width = 0; - _totalWidth = boundaryRect.left + boundaryRect.width; - _labelStyle = parent.chartThemeData!.trackballTextStyle!; - - _isTransposed = chartPointInfo.isNotEmpty && - chartPointInfo[0].series != null && - chartPointInfo[0].series!.isTransposed; - _tooltipPadding = _isTransposed ? 8 : 5; - ChartPointInfo? trackLinePoint = - chartPointInfo.isNotEmpty ? chartPointInfo[0] : null; - if (!_canResetPath && - trackLinePoint != null && - lineType != TrackballLineType.none) { - final Paint trackballLinePaint = Paint(); - trackballLinePaint.color = lineColor ?? _chartTheme!.crosshairLineColor; - trackballLinePaint.strokeWidth = lineWidth; - trackballLinePaint.style = PaintingStyle.stroke; - trackballLinePaint.isAntiAlias = true; - lineWidth == 0 - ? trackballLinePaint.color = Colors.transparent - : trackballLinePaint.color = trackballLinePaint.color; - _drawTrackBallLine(context.canvas, trackballLinePaint, 0); - } - if (builder == null) { - for (int index = 0; index < chartPointInfo.length; index++) { - final dynamic series = chartPointInfo[index].series; - - final ChartPointInfo next = chartPointInfo[index]; - final ChartPointInfo pres = trackLinePoint!; - final Offset pos = position!; - if (_isTransposed - ? ((pos.dy - pres.yPosition!).abs() >= - (pos.dy - next.yPosition!).abs()) - : ((pos.dx - pres.xPosition!).abs() >= - (pos.dx - next.xPosition!).abs())) { - trackLinePoint = chartPointInfo[index]; - } - final bool isRectTypeSeries = series != null && - (series is ColumnSeriesRenderer || - series is CandleSeriesRenderer || - series is BoxAndWhiskerSeriesRenderer || - series is HiloSeriesRenderer || - series is HiloOpenCloseSeriesRenderer); - _isRectSeries = false; - if ((isRectTypeSeries && _isTransposed) || - (series is BarSeriesRenderer && !_isTransposed)) { - _isRectSeries = true; - } - - final Size size = _tooltipSize(height, width, index); - height = size.height; - width = size.width; - if (width < 10) { - width = 10; // minimum width for tooltip to render - _borderRadius = _borderRadius > 5 ? 5 : _borderRadius; - } - _borderRadius = _borderRadius > 15 ? 15 : _borderRadius; - // Padding added for avoid tooltip and the data point are too close and - // extra padding based on trackball marker and width - _padding = (markerSettings != null && - markerSettings!.markerVisibility == - TrackballVisibilityMode.auto - ? chartPointInfo[index].series is IndicatorRenderer || - (series != null && series.markerSettings.isVisible == true) - : markerSettings != null && - markerSettings!.markerVisibility == - TrackballVisibilityMode.visible) - ? (markerSettings!.width / 2) + 5 - : _padding; - if (_x != null && _y != null && tooltipSettings.enable) { - if (_isGroupMode && - ((chartPointInfo[index].header != null && - chartPointInfo[index].header != '') || - (chartPointInfo[index].label != null && - chartPointInfo[index].label != ''))) { - for (int markerIndex = 0; - markerIndex < chartPointInfo.length; - markerIndex++) { - _renderEachMarkers(context.canvas, markerIndex); - } - _calculateTrackballRect( - context, width, height, index, chartPointInfo); - } else { - if (!_canResetPath && - chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - _tooltipTop.add(_isTransposed - ? visiblePoints[index].closestPointX - - _tooltipPadding - - (width / 2) - : visiblePoints[index].closestPointY - - _tooltipPadding - - height / 2); - _tooltipBottom.add(_isTransposed - ? (visiblePoints[index].closestPointX + - _tooltipPadding + - (width / 2)) + - (tooltipSettings.canShowMarker ? 20 : 0) - : visiblePoints[index].closestPointY + - _tooltipPadding + - height / 2); - if (series != null && series.xAxis != null) { - xAxesInfo.add(series.xAxis!); - } - if (series != null && series.yAxis != null) { - yAxesInfo.add(series.yAxis!); - } - } - } - } - - if (_isGroupMode) { - break; - } - } - -// ignore: unnecessary_null_comparison - if (_tooltipTop != null && _tooltipTop.isNotEmpty) { - tooltipPosition = _smartTooltipPositions(_tooltipTop, _tooltipBottom, - xAxesInfo, yAxesInfo, chartPointInfo, _isTransposed, true); - } - - for (int index = 0; index < chartPointInfo.length; index++) { - if (!_isGroupMode) { - _renderEachMarkers(context.canvas, index); - } - - // Padding added for avoid tooltip and the data point are too close and - // extra padding based on trackball marker and width - _padding = (markerSettings != null && - markerSettings!.markerVisibility == - TrackballVisibilityMode.auto - ? chartPointInfo[index].series is IndicatorRenderer || - (chartPointInfo[index].series!.markerSettings.isVisible == - true) - : markerSettings != null && - markerSettings!.markerVisibility == - TrackballVisibilityMode.visible) - ? (markerSettings!.width / 2) + 5 - : _padding; - if (tooltipSettings.enable && - !_isGroupMode && - chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - final Size size = _tooltipSize(height, width, index); - height = size.height; - width = size.width; - if (width < 10) { - width = 10; // minimum width for tooltip to render - _borderRadius = _borderRadius > 5 ? 5 : _borderRadius; - } - _calculateTrackballRect( - context, width, height, index, chartPointInfo, tooltipPosition); - if (index == chartPointInfo.length - 1) { - _tooltipTop.clear(); - _tooltipBottom.clear(); - tooltipPosition!.tooltipTop.clear(); - tooltipPosition!.tooltipBottom.clear(); - xAxesInfo.clear(); - yAxesInfo.clear(); - } - } - } - } - } - - void _generateAllPoints(Offset position) { - chartPointInfo = []; - visiblePoints = []; - _markerShapes.clear(); - RenderChartAxis xAxis, yAxis; - bool invertedAxis = false; - _tooltipTop = []; - _tooltipBottom = []; - _visibleLocation = []; - double? xPos = 0, - yPos = 0, - leastX = 0, - openXPos, - openYPos, - closeXPos, - closeYPos, - highXPos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos, - lowYPos, - highYPos, - minYPos, - maxYPos, - maxXPos; - int seriesIndex = 0, index; - CartesianChartPoint chartDataPoint; - String labelValue; - num? xValue, yValue, minimumValue, maximumValue, highValue, lowValue; - Rect axisClipRect; - ChartLocation highLocation, maxLocation; - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - - if (parent == null) { - return; - } - - final RenderChartPlotArea? cartesianPlotArea = parent.plotArea; - - if (cartesianPlotArea == null) { - return; - } - boundaryRect = parent.paintBounds; - final Rect seriesBounds = boundaryRect; - - cartesianPlotArea.visitChildren((RenderObject child) { - if (child is CartesianSeriesRenderer) { - _isRangeSeries = child is RangeSeriesRendererBase || - (child is HiloSeriesRenderer || - child is HiloOpenCloseSeriesRenderer) || - child is CandleSeriesRenderer; - _isBoxSeries = child is BoxAndWhiskerSeriesRenderer; - _isRectSeries = (child is ColumnSeriesRenderer || - child is RangeColumnSeriesRenderer) || - (child is StackedSeriesRenderer) || - child is BarSeriesRenderer || - child is HistogramSeriesRenderer || - child is WaterfallSeriesRenderer; - xAxis = child.xAxis!; - yAxis = child.yAxis!; - invertedAxis = child.isTransposed; - if (child.controller.isVisible && - child.dataSource != null && - child.dataSource!.isNotEmpty) { - final List nearestSegments = child.contains(position); - for (final ChartSegment segment in nearestSegments) { - final TrackballInfo? trackballInfo = - segment.trackballInfo(position); - if (trackballInfo != null) { - final ChartTrackballInfo pointInfo = - trackballInfo as ChartTrackballInfo; - index = segment.currentSegmentIndex; - if (index >= 0) { - chartDataPoint = pointInfo.point; - xValue = chartDataPoint.xValue; - if (child is! BoxAndWhiskerSeriesRenderer) { - yValue = chartDataPoint.y; - } - minimumValue = chartDataPoint.minimum; - maximumValue = chartDataPoint.maximum; - highValue = chartDataPoint.high; - lowValue = chartDataPoint.low; - axisClipRect = Rect.fromLTWH( - child.paintBounds.left + - (!_isTransposed ? xAxis.plotOffset : 0), - child.paintBounds.top + - (_isTransposed ? xAxis.plotOffset : 0), - child.paintBounds.width - - (!_isTransposed ? 2 * xAxis.plotOffset : 0), - child.paintBounds.height - - (_isTransposed ? 2 * yAxis.plotOffset : 0)); - - xPos = _calculatePoint(xValue!, yValue, xAxis, yAxis, - invertedAxis, child, axisClipRect) - .x; - if (!xPos!.isNaN) { - if (seriesIndex == 0 || - ((leastX! - position.dx).abs() > - (xPos! - position.dx).abs())) { - leastX = xPos; - } - labelValue = _trackballLabelText( - pointInfo, - pointInfo.point, - child, - ); - yPos = child is StackedSeriesRenderer - ? _calculatePoint( - xValue!, - child.topValues[pointInfo.pointIndex], - xAxis, - yAxis, - invertedAxis, - child, - axisClipRect) - .y - : _calculatePoint(xValue!, yValue, xAxis, yAxis, - invertedAxis, child, axisClipRect) - .y; - if (_isRangeSeries) { - lowYPos = _calculatePoint(xValue!, lowValue, xAxis, yAxis, - invertedAxis, child, axisClipRect) - .y; - highLocation = _calculatePoint(xValue!, highValue, xAxis, - yAxis, invertedAxis, child, axisClipRect); - highYPos = highLocation.y; - highXPos = highLocation.x; - } else if (child is BoxAndWhiskerSeriesRenderer) { - minYPos = _calculatePoint(xValue!, minimumValue, xAxis, - yAxis, invertedAxis, child, axisClipRect) - .y; - maxLocation = _calculatePoint(xValue!, maximumValue, xAxis, - yAxis, invertedAxis, child, axisClipRect); - maxXPos = maxLocation.x; - maxYPos = maxLocation.y; - } - final Rect rect = seriesBounds.intersect(Rect.fromLTWH( - xPos! - 1, - _isRangeSeries - ? highYPos! - 1 - : _isBoxSeries - ? maxYPos! - 1 - : yPos! - 1, - 2, - 2)); - if (seriesBounds.contains(Offset( - xPos!, - _isRangeSeries - ? highYPos! - : _isBoxSeries - ? maxYPos! - : yPos!)) || - seriesBounds.overlaps(rect)) { - visiblePoints.add(ClosestPoints( - closestPointX: !_isRangeSeries - ? xPos! - : _isBoxSeries - ? maxXPos! - : highXPos!, - closestPointY: _isRangeSeries - ? highYPos! - : _isBoxSeries - ? maxYPos! - : yPos!)); - _addChartPointInfo( - child, - xPos!, - yPos!, - index, - labelValue, - seriesIndex, - lowYPos, - highXPos, - highYPos, - openXPos, - openYPos, - closeXPos, - closeYPos, - minYPos, - maxXPos, - maxYPos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos, - chartDataPoint); - if (tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints && - leastX! >= seriesBounds.left) { - invertedAxis ? yPos = leastX : xPos = leastX; - } - } - } - } - } - } - seriesIndex++; - } - _validateNearestXValue(leastX!, child, position.dx, position.dy); - _validatePointsWithSeries(leastX!); - } - }); - - if (parent.indicatorArea != null) { - parent.indicatorArea!.visitChildren((RenderObject child) { - if (child is IndicatorRenderer && child.effectiveIsVisible) { - final List? trackballInfo = - child.trackballInfo(position); - if (trackballInfo != null && - trackballInfo.isNotEmpty && - child.animationFactor == 1) { - for (final TrackballInfo? info in trackballInfo) { - visiblePoints.add(ClosestPoints( - closestPointX: info!.position!.dx, - closestPointY: info.position!.dy)); - double? indicatorXPos = _calculatePoint( - (info as ChartTrackballInfo) - .point - .xValue!, - info.point.y, - child.xAxis!, - child.yAxis!, - invertedAxis, - child, - child.paintBounds) - .x; - if ((leastX! - position.dx).abs() > - (indicatorXPos - position.dx).abs()) { - leastX = indicatorXPos; - } - - double indicatorYPos = _calculatePoint( - info.point.xValue!, - info.point.y, - child.xAxis!, - child.yAxis!, - invertedAxis, - child, - child.paintBounds) - .y; - - if (invertedAxis && - (leastX! - position.dx).abs() > - (indicatorYPos - position.dx).abs()) { - leastX = indicatorYPos; - } - - final String labelValue = _trackballLabelText( - info, - info.point, - child, - ); - if (info.pointIndex > -1) { - _addChartPointInfo( - child, - indicatorXPos, - indicatorYPos, - info.pointIndex, - labelValue, - seriesIndex, - lowYPos, - highXPos, - highYPos, - openXPos, - openYPos, - closeXPos, - closeYPos, - minYPos, - maxXPos, - maxYPos, - lowerXPos, - lowerYPos, - upperXPos, - upperYPos, - info.point, - info.name, - info.color, - ); - if (tooltipDisplayMode == TrackballDisplayMode.groupAllPoints && - leastX! >= seriesBounds.left && - leastX != null) { - invertedAxis - ? indicatorYPos = leastX! - : indicatorXPos = leastX; - } - } - } - } - } - _validateNearestXValue(leastX!, child, position.dx, position.dy); - _validatePointsWithSeries(leastX!); - }); - } - - if (visiblePoints.isNotEmpty) { - invertedAxis - ? visiblePoints.sort((ClosestPoints a, ClosestPoints b) => - a.closestPointX.compareTo(b.closestPointX)) - : visiblePoints.sort((ClosestPoints a, ClosestPoints b) => - a.closestPointY.compareTo(b.closestPointY)); - } - if (chartPointInfo.isNotEmpty) { - if (tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { - invertedAxis - ? chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - a.xPosition!.compareTo(b.xPosition!)) - : tooltipDisplayMode == TrackballDisplayMode.floatAllPoints - ? chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - a.yPosition!.compareTo(b.yPosition!)) - : chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => - b.yPosition!.compareTo(a.yPosition!)); - } - if (tooltipDisplayMode == TrackballDisplayMode.nearestPoint || - (_isRectSeries == true && - tooltipDisplayMode != TrackballDisplayMode.groupAllPoints)) { - _validateNearestPointForAllSeries( - leastX!, chartPointInfo, position.dx, position.dy); - } - } - _triggerTrackballRenderCallback(); - } - - /// Event for trackball render - void _triggerTrackballRenderCallback() { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - if (parent.onTrackballPositionChanging != null) { - int index; - for (index = chartPointInfo.length - 1; index >= 0; index--) { - TrackballArgs chartPoint; - chartPoint = TrackballArgs(); - chartPoint.chartPointInfo = chartPointInfo[index]; - parent.onTrackballPositionChanging!(chartPoint); - chartPointInfo[index].label = chartPoint.chartPointInfo.label; - chartPointInfo[index].header = chartPoint.chartPointInfo.header; - if (!_isTrackballTemplate && chartPointInfo[index].label == null || - chartPointInfo[index].label == '') { - chartPointInfo.removeAt(index); - visiblePoints.removeAt(index); - } - } - } - } - - /// To validate the nearest point in all series for trackball - void _validateNearestPointForAllSeries(double leastX, - List trackballInfo, double touchXPos, double touchYPos) { - double xPos = 0, yPos; - final List tempTrackballInfo = - List.from(trackballInfo); - ChartPointInfo pointInfo; - num? yValue; - num xValue; - Rect axisClipRect; - RenderChartAxis xAxisDetails, yAxisDetails; - int i; - // late List data; - for (i = 0; i < tempTrackballInfo.length; i++) { - pointInfo = tempTrackballInfo[i]; - xAxisDetails = pointInfo.series!.xAxis!; - yAxisDetails = pointInfo.series!.yAxis!; - xValue = pointInfo.series!.xValues[pointInfo.dataPointIndex!]; - if (pointInfo.series is! BoxAndWhiskerSeriesRenderer) { - yValue = pointInfo.chartDataPoint!.y; - } - axisClipRect = pointInfo.series!.paintBounds; - final ChartLocation chartPointOffset = _calculatePoint( - xValue, - yValue, - xAxisDetails, - yAxisDetails, - pointInfo.series!.isTransposed, - pointInfo.series, - axisClipRect); - - xPos = chartPointOffset.x; - yPos = chartPointOffset.y; - if (tooltipDisplayMode != TrackballDisplayMode.floatAllPoints) { - final bool isTransposed = pointInfo.series!.isTransposed; - if (leastX != xPos && !isTransposed) { - trackballInfo.remove(pointInfo); - } - yPos = touchYPos; - xPos = touchXPos; - if (tooltipDisplayMode != TrackballDisplayMode.floatAllPoints) { - ChartPointInfo point = trackballInfo[0]; - for (i = 1; i < trackballInfo.length; i++) { - final bool isXYPositioned = !isTransposed - ? (((point.yPosition! - yPos).abs() > - (trackballInfo[i].yPosition! - yPos).abs()) && - point.xPosition == trackballInfo[i].xPosition) - : (((point.xPosition! - xPos).abs() > - (trackballInfo[i].xPosition! - xPos).abs()) && - point.yPosition == trackballInfo[i].yPosition); - if (isXYPositioned) { - point = trackballInfo[i]; - } - } - trackballInfo - ..clear() - ..add(point); - } - } - } - } - - /// To find the nearest x value to render a trackball - void _validateNearestXValue( - double leastX, dynamic series, double touchXPos, double touchYPos) { - final List leastPointInfo = []; - final bool invertedAxis = series.isTransposed; - double nearPointX = - invertedAxis ? series.paintBounds.top : series.paintBounds.left; - final double touchXValue = invertedAxis ? touchYPos : touchXPos; - double delta = 0, curX; - num xValue; - num? yValue; - CartesianChartPoint dataPoint; - RenderChartAxis xAxisDetails, yAxisDetails; - ChartLocation curXLocation; - for (final ChartPointInfo pointInfo in chartPointInfo) { - if (pointInfo.dataPointIndex! < series.dataCount) { - dataPoint = pointInfo.chartDataPoint!; - xAxisDetails = pointInfo.series!.xAxis!; - yAxisDetails = pointInfo.series!.yAxis!; - xValue = dataPoint.xValue!; - if (series is BoxAndWhiskerSeriesRenderer) { - yValue = dataPoint.y; - } - series = pointInfo.series; - curXLocation = _calculatePoint(xValue, yValue, xAxisDetails, - yAxisDetails, invertedAxis, series, series.paintBounds); - curX = invertedAxis ? curXLocation.y : curXLocation.x; - - if (delta == touchXValue - curX) { - leastPointInfo.add(pointInfo); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - delta = touchXValue - curX; - leastPointInfo.clear(); - leastPointInfo.add(pointInfo); - } - } - if (chartPointInfo.isNotEmpty && series is CartesianSeriesRenderer) { - if (chartPointInfo[0].dataPointIndex! < series.dataCount) { - leastX = _findLeastX(chartPointInfo[0], series); - } - } - - if (pointInfo.series! is BarSeriesRenderer - ? invertedAxis - : invertedAxis) { - _yPos = leastX; - } else { - _xPos = leastX; - } - } - } - - void _validatePointsWithSeries(double leastX) { - final List xValueList = []; - for (final ChartPointInfo pointInfo in chartPointInfo) { - xValueList.add(pointInfo.chartDataPoint!.xValue!); - } - dynamic series; - bool isRangeTypeSeries; - if (xValueList.isNotEmpty) { - for (int count = 0; count < xValueList.length; count++) { - if (xValueList[0] != xValueList[count]) { - final List leastPointInfo = []; - for (final ChartPointInfo pointInfo in chartPointInfo) { - if (pointInfo.xPosition == leastX) { - leastPointInfo.add(pointInfo); - if (!(tooltipDisplayMode == TrackballDisplayMode.floatAllPoints && - leastPointInfo.length > 1 && - (pointInfo.series is IndicatorRenderer || - pointInfo.seriesIndex != - leastPointInfo[leastPointInfo.length - 2] - .seriesIndex))) { - visiblePoints.clear(); - } - series = pointInfo.series; - isRangeTypeSeries = series is RangeSeriesRendererBase || - series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer || - series is CandleSeriesRenderer; - visiblePoints.add(ClosestPoints( - closestPointX: isRangeTypeSeries - ? pointInfo.highXPosition! - : series is BoxAndWhiskerSeriesRenderer - ? pointInfo.maxXPosition! - : pointInfo.xPosition!, - closestPointY: isRangeTypeSeries - ? pointInfo.highYPosition! - : series is BoxAndWhiskerSeriesRenderer - ? pointInfo.maxYPosition! - : pointInfo.yPosition!)); - } - } - chartPointInfo.clear(); - chartPointInfo = leastPointInfo; - } - } - } - } - - /// To get the lowest x value to render trackball - double _findLeastX(ChartPointInfo pointInfo, dynamic series) { - return _calculatePoint(pointInfo.chartDataPoint!.xValue!, 0, series.xAxis!, - series.yAxis!, series.isTransposed, series, series.paintBounds) - .x; - } - - /// Get the location of point. - ChartLocation _calculatePoint(num x, num? y, RenderChartAxis xAxis, - RenderChartAxis yAxis, bool isInverted, dynamic series, Rect rect) { - x = xAxis is RenderLogarithmicAxis - ? _calculateLogBaseValue(x > 0 ? x : 0, xAxis.logBase) - : x; - y = yAxis is RenderLogarithmicAxis - ? y != null - ? _calculateLogBaseValue(y > 0 ? y : 0, yAxis.logBase) - : 0 - : y; - x = _valueToCoefficient(x.isInfinite ? 0 : x, xAxis); - y = _valueToCoefficient( - y != null - ? y.isInfinite - ? 0 - : y - : y, - yAxis); - final num xLength = isInverted ? rect.height : rect.width; - final num yLength = isInverted ? rect.width : rect.height; - final double locationX = - rect.left + (isInverted ? (y * yLength) : (x * xLength)); - final double locationY = - rect.top + (isInverted ? (1 - x) * xLength : (1 - y) * yLength); - return ChartLocation(locationX, locationY); - } - - /// Find the position of point. - num _valueToCoefficient(num? value, RenderChartAxis axisRendererDetails) { - num result = 0; - if (axisRendererDetails.visibleRange != null && value != null) { - final DoubleRange range = axisRendererDetails.visibleRange!; - // ignore: unnecessary_null_comparison - if (range != null) { - result = (value - range.minimum) / (range.delta); - result = axisRendererDetails.isInversed ? (1 - result) : result; - } - } - return result; - } - - /// To get and return label text of the trackball - String _trackballLabelText( - ChartTrackballInfo info, ChartPoint dataPoint, dynamic series) { - String labelValue; - final int digits = tooltipSettings.decimalPlaces; - final dynamic currentSeries = series; - final RenderChartAxis? yAxis = currentSeries.yAxis; - if (tooltipSettings.format != null) { - dynamic x; - final RenderChartAxis? xAxis = currentSeries.xAxis; - if (currentSeries.xAxis is RenderDateTimeAxis && xAxis != null) { - final num interval = xAxis.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxis.visibleLabels.isNotEmpty) - ? xAxis.visibleLabels[xAxis.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = (xAxis as RenderDateTimeAxis) - .dateFormat ?? - _dateTimeLabelFormat(xAxis, interval.toInt(), prevInterval.toInt()); - x = dateFormat.format(info.point.x as DateTime); - } else if (xAxis is RenderCategoryAxis) { - x = dataPoint.x; - } else if (xAxis is RenderDateTimeCategoryAxis) { - final num interval = xAxis.visibleRange!.minimum.ceil(); - final num prevInterval = (xAxis.visibleLabels.isNotEmpty) - ? xAxis.visibleLabels[xAxis.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = (xAxis as DateTimeCategoryAxis) - .dateFormat ?? - _dateTimeLabelFormat(xAxis, interval.toInt(), prevInterval.toInt()); - x = dateFormat.format(DateTime.fromMillisecondsSinceEpoch( - dataPoint.x.millisecondsSinceEpoch)); - } - // ignore: lines_longer_than_80_chars - labelValue = (currentSeries is HiloOpenCloseSeriesRenderer || currentSeries is HiloSeriesRenderer) || - (currentSeries is RangeSeriesRendererBase) || - currentSeries is CandleSeriesRenderer || - currentSeries is BoxAndWhiskerSeriesRenderer - ? currentSeries is BoxAndWhiskerSeriesRenderer - ? (tooltipSettings.format! - .replaceAll('point.x', (x ?? info.point.x).toString()) - .replaceAll('point.minimum', info.point.minimum.toString()) - .replaceAll('point.maximum', info.point.maximum.toString()) - .replaceAll('point.lowerQuartile', - info.point.lowerQuartile.toString()) - .replaceAll('point.upperQuartile', - info.point.upperQuartile.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', currentSeries.name)) - : currentSeries is HiloSeriesRenderer || - series is RangeSeriesRendererBase - ? (tooltipSettings.format! - .replaceAll('point.x', (x ?? info.point.x).toString()) - .replaceAll('point.high', info.point.high.toString()) - .replaceAll('point.low', info.point.low.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', currentSeries.name)) - : (tooltipSettings.format! - .replaceAll('point.x', (x ?? info.point.x).toString()) - .replaceAll('point.high', info.point.high.toString()) - .replaceAll('point.low', info.point.low.toString()) - .replaceAll('point.open', info.point.open.toString()) - .replaceAll('point.close', info.point.close.toString()) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', currentSeries.name)) - : currentSeries is BubbleSeriesRenderer - ? (tooltipSettings.format! - .replaceAll('point.x', (x ?? info.point.x).toString()) - .replaceAll('point.y', - _labelValue(info.point.y!, currentSeries.yAxis, digits)) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', currentSeries.name) - .replaceAll('point.size', info.point.bubbleSize.toString())) - : series is StackedSeriesRenderer - ? (tooltipSettings.format! - .replaceAll('point.x', (x ?? info.point.x).toString()) - .replaceAll('point.y', _labelValue(info.point.y!, currentSeries.yAxis, digits)) - .replaceAll('{', '') - .replaceAll('}', '') - .replaceAll('series.name', currentSeries.name) - .replaceAll('point.cumulativeValue', info.point.cumulative.toString())) - : (tooltipSettings.format!.replaceAll('point.x', (x ?? info.point.x).toString()).replaceAll('point.y', _labelValue(info.point.y!, yAxis, digits)).replaceAll('{', '').replaceAll('}', '').replaceAll('series.name', currentSeries.name)); - } else { - labelValue = (series is! RangeSeriesRendererBase) && - series is! CandleSeriesRenderer && - (series is! HiloOpenCloseSeriesRenderer && - series is! HiloSeriesRenderer) && - series is! BoxAndWhiskerSeriesRenderer - ? _labelValue(info.point.y!, yAxis, digits) - : series is HiloOpenCloseSeriesRenderer || - series is CandleSeriesRenderer || - series is BoxAndWhiskerSeriesRenderer - ? series is BoxAndWhiskerSeriesRenderer - // ignore: lines_longer_than_80_chars - ? 'Maximum : ${_labelValue(info.point.maximum!, yAxis)}\n' - 'Minimum : ${_labelValue(info.point.minimum!, yAxis)}\n' - 'LowerQuartile :' - ' ${_labelValue(info.point.lowerQuartile!, yAxis)}\n' - 'UpperQuartile :' - ' ${_labelValue(info.point.upperQuartile!, yAxis)}' - : 'High : ${_labelValue(info.point.high!, yAxis)}\n' - 'Low : ${_labelValue(info.point.low!, yAxis)}\n' - 'Open : ${_labelValue(info.point.open!, yAxis)}\n' - 'Close : ${_labelValue(info.point.close!, yAxis)}' - : 'High : ${_labelValue(info.point.high!, yAxis)}\n' - 'Low : ${_labelValue(info.point.low!, yAxis)}'; - } - return labelValue; - } - - /// To add chart point info - void _addChartPointInfo( - dynamic series, - double xPos, - double yPos, - int dataPointIndex, - String? label, - int seriesIndex, [ - double? lowYPos, - double? highXPos, - double? highYPos, - double? openXPos, - double? openYPos, - double? closeXPos, - double? closeYPos, - double? minYPos, - double? maxXPos, - double? maxYPos, - double? lowerXPos, - double? lowerYPos, - double? upperXPos, - double? upperYPos, - CartesianChartPoint? point, - String? name, - Color? color, - ]) { - final ChartPointInfo pointInfo = ChartPointInfo(); - - pointInfo.seriesName = name; - pointInfo.series = series; - pointInfo.markerXPos = xPos; - pointInfo.markerYPos = yPos; - pointInfo.xPosition = xPos; - pointInfo.yPosition = yPos; - pointInfo.seriesIndex = seriesIndex; - - if ((series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - (series is RangeSeriesRendererBase) || - series is SplineRangeAreaSeriesRenderer || - series is CandleSeriesRenderer) { - pointInfo.lowYPosition = lowYPos; - pointInfo.highXPosition = highXPos; - pointInfo.highYPosition = highYPos; - if (series is HiloOpenCloseSeriesRenderer || - series is CandleSeriesRenderer) { - pointInfo.openXPosition = openXPos; - pointInfo.openYPosition = openYPos; - pointInfo.closeXPosition = closeXPos; - pointInfo.closeYPosition = closeYPos; - } - } else if (series is BoxAndWhiskerSeriesRenderer) { - pointInfo.minYPosition = minYPos; - pointInfo.maxYPosition = maxYPos; - pointInfo.maxXPosition = maxXPos; - pointInfo.lowerXPosition = lowerXPos; - pointInfo.lowerYPosition = lowerYPos; - pointInfo.upperXPosition = upperXPos; - pointInfo.upperYPosition = upperYPos; - } - - if (series.dataSource != null && - series.dataSource!.length > dataPointIndex) { - pointInfo.color = series is! CartesianSeriesRenderer - ? color - : series.color ?? series.segments[dataPointIndex].fillPaint.color; - } else if (series.dataSource != null && series.dataSource!.length > 1) { - pointInfo.color = series is! CartesianSeriesRenderer - ? color - : series.color ?? series.segments[dataPointIndex].fillPaint.color; - } else if (color != null) { - pointInfo.color = color; - } - pointInfo.chartDataPoint = point; - pointInfo.dataPointIndex = dataPointIndex; - if (!_isTrackballTemplate) { - pointInfo.label = label; - pointInfo.header = _findHeaderText(point!, series.xAxis!); - } - chartPointInfo.add(pointInfo); - } - - void _renderEachMarkers(Canvas canvas, int markerIndex) { - _trackballMarker(markerIndex); - if (_markerShapes.isNotEmpty && _markerShapes.length > markerIndex) { - _renderTrackballMarker(canvas, markerIndex); - } - } - - /// To render the trackball marker for both tooltip and template - void _trackballMarker(int index) { - if (markerSettings != null && - (markerSettings!.markerVisibility == TrackballVisibilityMode.auto - ? chartPointInfo[index].series is IndicatorRenderer || - (chartPointInfo[index].series != null && - chartPointInfo[index].series!.markerSettings.isVisible == - true) - : markerSettings!.markerVisibility == - TrackballVisibilityMode.visible)) { - final DataMarkerType markerType = markerSettings!.shape; - final Size size = Size(markerSettings!.width, markerSettings!.height); - final dynamic series = chartPointInfo[index].series; - _markerShapes.add(_markerShapesPath( - markerType, - Offset( - chartPointInfo[index].xPosition!, - (series != null && (series is RangeSeriesRendererBase) || - (series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - series is CandleSeriesRenderer) - ? chartPointInfo[index].highYPosition! - : series is BoxAndWhiskerSeriesRenderer - ? chartPointInfo[index].maxYPosition! - : chartPointInfo[index].yPosition!), - size, - chartPointInfo[index].series)); - } - } - - /// To render the trackball marker - void _renderTrackballMarker(Canvas canvas, int index) { - final Paint strokePaint = Paint() - ..color = markerSettings!.borderWidth == 0 - ? Colors.transparent - : markerSettings!.borderColor ?? chartPointInfo[index].color! - ..strokeWidth = markerSettings!.borderWidth - ..style = PaintingStyle.stroke - ..isAntiAlias = true; - - final Paint fillPaint = Paint() - ..color = markerSettings!.color ?? - (_chartTheme!.brightness == Brightness.light - ? Colors.white - : Colors.black) - ..style = PaintingStyle.fill - ..isAntiAlias = true; - if (index < _markerShapes.length) { - canvas.drawPath(_markerShapes[index], strokePaint); - canvas.drawPath(_markerShapes[index], fillPaint); - } - } - - /// Method to place the collided tooltips properly - TooltipPositions _smartTooltipPositions( - List tooltipTop, - List tooltipBottom, - List xAxesInfo, - List yAxesInfo, - List chartPointInfo, - bool requireInvertedAxis, - [bool? isPainterTooltip]) { - _tooltipPadding = _isTransposed ? 8 : 5; - num tooltipWidth = 0; - TooltipPositions tooltipPosition; - for (int i = 0; i < chartPointInfo.length; i++) { - final dynamic series = chartPointInfo[i].series; - requireInvertedAxis - ? _visibleLocation.add(chartPointInfo[i].xPosition!) - : _visibleLocation.add(((series is RangeSeriesRendererBase) || - (series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - series is CandleSeriesRenderer) - ? chartPointInfo[i].highYPosition! - : series is BoxAndWhiskerSeriesRenderer - ? chartPointInfo[i].maxYPosition! - : chartPointInfo[i].yPosition!); - - tooltipWidth += tooltipBottom[i] - tooltipTop[i] + _tooltipPadding; - } - tooltipPosition = _continuousOverlappingPoints( - tooltipTop, tooltipBottom, _visibleLocation); - - if (!requireInvertedAxis - ? tooltipWidth < (boundaryRect.bottom - boundaryRect.top) - : tooltipWidth < (boundaryRect.right - boundaryRect.left)) { - tooltipPosition = - _verticalArrangements(tooltipPosition, xAxesInfo, yAxesInfo); - } - return tooltipPosition; - } - - TooltipPositions _verticalArrangements(TooltipPositions tooltipPosition, - List xAxesInfo, List yAxesInfo) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return tooltipPosition; - } - num? startPos, chartHeight; - num secWidth, width; - final int length = tooltipPosition.tooltipTop.length; - RenderChartAxis yAxis; - for (int i = length - 1; i >= 0; i--) { - yAxis = yAxesInfo[i]; - RenderChartAxis? child = parent.cartesianAxes!.firstChild; - while (child != null) { - if (yAxis == child) { - if (_isTransposed) { - chartHeight = boundaryRect.right; - startPos = boundaryRect.left; - } else { - chartHeight = boundaryRect.bottom - boundaryRect.top; - startPos = boundaryRect.top; - } - } - - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; - if (chartHeight != null && - chartHeight < tooltipPosition.tooltipBottom[i]) { - tooltipPosition.tooltipBottom[i] = chartHeight - 2; - tooltipPosition.tooltipTop[i] = - tooltipPosition.tooltipBottom[i] - width; - for (int j = i - 1; j >= 0; j--) { - secWidth = - tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; - if (tooltipPosition.tooltipBottom[j] > - tooltipPosition.tooltipTop[j + 1] && - (tooltipPosition.tooltipTop[j + 1] > startPos! && - tooltipPosition.tooltipBottom[j + 1] < chartHeight)) { - tooltipPosition.tooltipBottom[j] = - tooltipPosition.tooltipTop[j + 1] - _tooltipPadding; - tooltipPosition.tooltipTop[j] = - tooltipPosition.tooltipBottom[j] - secWidth; - } - } - } - } - for (int i = 0; i < length; i++) { - yAxis = yAxesInfo[i]; - - RenderChartAxis? child = parent.cartesianAxes!.firstChild; - while (child != null) { - if (yAxis == child) { - if (_isTransposed) { - chartHeight = boundaryRect.right; - startPos = boundaryRect.left; - } else { - chartHeight = boundaryRect.bottom - boundaryRect.top; - startPos = boundaryRect.top; - } - } - - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - width = tooltipPosition.tooltipBottom[i] - tooltipPosition.tooltipTop[i]; - if (startPos != null && tooltipPosition.tooltipTop[i] < startPos) { - tooltipPosition.tooltipTop[i] = startPos + 1; - tooltipPosition.tooltipBottom[i] = - tooltipPosition.tooltipTop[i] + width; - for (int j = i + 1; j <= (length - 1); j++) { - secWidth = - tooltipPosition.tooltipBottom[j] - tooltipPosition.tooltipTop[j]; - if (tooltipPosition.tooltipTop[j] < - tooltipPosition.tooltipBottom[j - 1] && - (tooltipPosition.tooltipTop[j - 1] > startPos && - tooltipPosition.tooltipBottom[j - 1] < chartHeight!)) { - tooltipPosition.tooltipTop[j] = - tooltipPosition.tooltipBottom[j - 1] + _tooltipPadding; - tooltipPosition.tooltipBottom[j] = - tooltipPosition.tooltipTop[j] + secWidth; - } - } - } - } - return tooltipPosition; - } - - // Method to identify the colliding trackball tooltips and return the new tooltip positions - TooltipPositions _continuousOverlappingPoints(List tooltipTop, - List tooltipBottom, List visibleLocation) { - num temp, - count = 0, - start = 0, - halfHeight, - midPos, - tempTooltipHeight, - temp1TooltipHeight; - int startPoint = 0, i, j, k; - final num endPoint = tooltipBottom.length - 1; - num tooltipHeight = (tooltipBottom[0] - tooltipTop[0]) + _tooltipPadding; - temp = tooltipTop[0] + tooltipHeight; - start = tooltipTop[0]; - for (i = 0; i < endPoint; i++) { - // To identify that tooltip collides or not - if (temp >= tooltipTop[i + 1]) { - tooltipHeight = - tooltipBottom[i + 1] - tooltipTop[i + 1] + _tooltipPadding; - temp += tooltipHeight; - count++; - // This condition executes when the tooltip count is half of the total number of tooltips - if (count - 1 == endPoint - 1 || i == endPoint - 1) { - halfHeight = (temp - start) / 2; - midPos = (visibleLocation[startPoint] + visibleLocation[i + 1]) / 2; - tempTooltipHeight = - tooltipBottom[startPoint] - tooltipTop[startPoint]; - tooltipTop[startPoint] = midPos - halfHeight; - tooltipBottom[startPoint] = - tooltipTop[startPoint] + tempTooltipHeight; - for (k = startPoint; k > 0; k--) { - if (tooltipTop[k] <= tooltipBottom[k - 1] + _tooltipPadding) { - temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; - tooltipTop[k - 1] = - tooltipTop[k] - temp1TooltipHeight - _tooltipPadding; - tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; - } else { - break; - } - } - // To set tool tip positions based on the half height and other tooltip height - for (j = startPoint + 1; j <= startPoint + count; j++) { - tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; - tooltipTop[j] = tooltipBottom[j - 1] + _tooltipPadding; - tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; - } - } - } else { - count = i > 0 ? count : 0; - // This executes when any of the middle tooltip collides - if (count > 0) { - halfHeight = (temp - start) / 2; - midPos = (visibleLocation[startPoint] + visibleLocation[i]) / 2; - tempTooltipHeight = - tooltipBottom[startPoint] - tooltipTop[startPoint]; - tooltipTop[startPoint] = midPos - halfHeight; - tooltipBottom[startPoint] = - tooltipTop[startPoint] + tempTooltipHeight; - for (k = startPoint; k > 0; k--) { - if (tooltipTop[k] <= tooltipBottom[k - 1] + _tooltipPadding) { - temp1TooltipHeight = tooltipBottom[k - 1] - tooltipTop[k - 1]; - tooltipTop[k - 1] = - tooltipTop[k] - temp1TooltipHeight - _tooltipPadding; - tooltipBottom[k - 1] = tooltipTop[k - 1] + temp1TooltipHeight; - } else { - break; - } - } - - // To set tool tip positions based on the half height and other tooltip height - for (j = startPoint + 1; j <= startPoint + count; j++) { - tempTooltipHeight = tooltipBottom[j] - tooltipTop[j]; - tooltipTop[j] = tooltipBottom[j - 1] + _tooltipPadding; - tooltipBottom[j] = tooltipTop[j] + tempTooltipHeight; - } - count = 0; - } - tooltipHeight = - (tooltipBottom[i + 1] - tooltipTop[i + 1]) + _tooltipPadding; - temp = tooltipTop[i + 1] + tooltipHeight; - start = tooltipTop[i + 1]; - startPoint = i + 1; - } - } - return TooltipPositions(tooltipTop, tooltipBottom); - } - - /// To get tooltip size. - Size _tooltipSize(double height, double width, int index) { - final Offset position = Offset( - chartPointInfo[index].xPosition!, chartPointInfo[index].yPosition!); - _stringValue = []; - final String? format = tooltipSettings.format; - if (format != null && - format.contains('point.x') && - !format.contains('point.y')) { - _xFormat = true; - } - if (format != null && - format.contains('point.x') && - format.contains('point.y') && - !format.contains(':')) { - _isColon = false; - } - if (chartPointInfo[index].header != null && - chartPointInfo[index].header != '') { - _stringValue.add(TrackballElement(chartPointInfo[index].header!, null)); - } - if (_isGroupMode) { - String str1 = ''; - for (int i = 0; i < chartPointInfo.length; i++) { - // pos = position; - final dynamic series = chartPointInfo[i].series; - if (chartPointInfo[i].header != null && - chartPointInfo[i].header!.contains(':')) { - _headerText = true; - } - final bool isHeader = - chartPointInfo[i].header != null && chartPointInfo[i].header != ''; - final bool isLabel = - chartPointInfo[i].label != null && chartPointInfo[i].label != ''; - _divider = isHeader && isLabel; - if (series.runtimeType.toString().contains('rangearea') == true) { - if (i == 0) { - _stringValue.add(TrackballElement('', null)); - } else { - str1 = ''; - } - continue; - } else if (((series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - series is CandleSeriesRenderer || - (series is RangeSeriesRendererBase) || - series is BoxAndWhiskerSeriesRenderer) && - tooltipSettings.format == null && - isLabel) { - _stringValue.add(TrackballElement( - '${(chartPointInfo[index].header == null || chartPointInfo[index].header == '') ? '' : i == 0 ? '\n' : ''}${series!.name}\n${chartPointInfo[i].label}', - series)); - } else if (series is IndicatorRenderer || - series!.name != series.localizedName()) { - if (tooltipSettings.format != null) { - if (isHeader && isLabel && i == 0) { - _stringValue.add(TrackballElement('', null)); - } - if (isLabel) { - _stringValue.add(TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].series)); - } - } else if (isLabel && - chartPointInfo[i].label!.contains(':') && - (chartPointInfo[i].header == null || - chartPointInfo[i].header == '')) { - _stringValue.add(TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].series)); - _divider = false; - } else { - if (isHeader && isLabel && i == 0) { - _stringValue.add(TrackballElement('', null)); - } - if (isLabel && series != null) { - if (series is IndicatorRenderer) { - _stringValue.add(TrackballElement( - '$str1${chartPointInfo[i].seriesName}: ${chartPointInfo[i].label!}', - chartPointInfo[i].series)); - } else { - _stringValue.add(TrackballElement( - '$str1${chartPointInfo[i].series!.name}: ${chartPointInfo[i].label!}', - chartPointInfo[i].series)); - } - } - _divider = (chartPointInfo[0].header != null && - chartPointInfo[0].header != '') && - isLabel; - } - if (str1 != '') { - str1 = ''; - } - } else { - if (isLabel) { - if (isHeader && i == 0) { - _stringValue.add(TrackballElement('', null)); - } - _stringValue.add(TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].series)); - } - } - } - for (int i = 0; i < _stringValue.length; i++) { - String measureString = _stringValue[i].label; - if (measureString.contains('') && measureString.contains('')) { - measureString = - measureString.replaceAll('', '').replaceAll('', ''); - } - if (measureText(measureString, _labelStyle).width > width) { - width = measureText(measureString, _labelStyle).width; - } - height += measureText(measureString, _labelStyle).height; - } - if (_isTransposed) { - _y = position.dy; - _x = (tooltipAlignment == ChartAlignment.center) - ? boundaryRect.center.dx - : (tooltipAlignment == ChartAlignment.near) - ? boundaryRect.top - : boundaryRect.bottom; - } else { - _x = position.dx; - _y = (tooltipAlignment == ChartAlignment.center) - ? boundaryRect.center.dy - : (tooltipAlignment == ChartAlignment.near) - ? boundaryRect.top - : boundaryRect.bottom; - } - } else { - final dynamic series = chartPointInfo[index].series; - _stringValue = []; - if (chartPointInfo[index].label != null && - chartPointInfo[index].label != '') { - _stringValue.add(TrackballElement( - chartPointInfo[index].label!, chartPointInfo[index].series)); - } - - String? measureString = - _stringValue.isNotEmpty ? _stringValue[0].label : null; - if (measureString != null && - measureString.contains('') && - measureString.contains('')) { - measureString = - measureString.replaceAll('', '').replaceAll('', ''); - } - final Size size = measureText(measureString!, _labelStyle); - width = size.width; - height = size.height; - - if (series is ColumnSeriesRenderer || - series is BarSeriesRenderer || - series is CandleSeriesRenderer || - series is BoxAndWhiskerSeriesRenderer || - (series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer)) { - _x = position.dx; - _y = position.dy; - } else { - _x = position.dx; - _y = position.dy; - } - } - return Size(width, height); - } - - /// To get header text of trackball - String _findHeaderText(CartesianChartPoint point, RenderChartAxis axis) { - String headerText; - String? date; - if (axis is RenderDateTimeAxis) { - final RenderDateTimeAxis xAxis = axis; - final num interval = axis.visibleRange!.minimum.ceil(); - final num xValue = point.xValue!; - final num prevInterval = (axis.visibleLabels.isNotEmpty) - ? axis.visibleLabels[axis.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = xAxis.dateFormat ?? - _dateTimeLabelFormat(axis, interval.toInt(), prevInterval.toInt()); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } - headerText = axis is RenderCategoryAxis - ? point.x.toString() - : axis is RenderDateTimeAxis - ? date ?? '' - : (axis is RenderDateTimeCategoryAxis - ? _formattedLabel( - '${point.x.microsecondsSinceEpoch}', axis.dateFormat!) - : _labelValue( - point.x, axis, axis.interactiveTooltip.decimalPlaces)); - return headerText; - } - - String _formattedLabel(String label, DateFormat dateFormat) { - return dateFormat - .format(DateTime.fromMicrosecondsSinceEpoch(int.parse(label))); - } - - /// To find the rect location of the trackball. - void _calculateTrackballRect( - PaintingContext context, double width, double height, int index, - [List? chartPointInfo, - TooltipPositions? tooltipPosition]) { - final dynamic series = chartPointInfo![index].series; - const double widthPadding = 17; - _markerSize = 10; - if (!tooltipSettings.canShowMarker) { - _labelRect = Rect.fromLTWH(_x!, _y!, width + 15, height + 10); - } else { - _labelRect = Rect.fromLTWH( - _x!, _y!, width + (2 * _markerSize) + widthPadding, height + 10); - } - - if (_y! > _pointerLength + _labelRect.height) { - _calculateTooltipSize(_labelRect, chartPointInfo, index); - } else { - _isTop = false; - if (series is BarSeriesRenderer ? _isTransposed : _isTransposed) { - _xPos = _x! - (_labelRect.width / 2); - _yPos = (_y! + _pointerLength) + _padding; - _nosePointX = _labelRect.left; - _nosePointY = _labelRect.top + _padding; - final double tooltipRightEnd = _x! + (_labelRect.width / 2); - _xPos = _xPos! < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > _totalWidth - ? _totalWidth - _labelRect.width - : _xPos; - } else { - if (_isGroupMode) { - _xPos = _x! - _labelRect.width / 2; - _yPos = _y! - _labelRect.height / 2; - } else { - _xPos = _x; - _yPos = (_y! + _pointerLength / 2) + _padding; - } - _nosePointX = _labelRect.left; - _nosePointY = _labelRect.top; - if (!_isRtl) { - if ((_isGroupMode - ? (_xPos! + (_labelRect.width / 2) + groupAllPadding) - : _xPos! + _labelRect.width + _padding + _pointerLength) > - boundaryRect.right) { - _xPos = _isGroupMode - ? (_xPos! - (_labelRect.width / 2) - groupAllPadding) - : _xPos! - _labelRect.width - _padding - _pointerLength; - _isLeft = true; - } else { - _xPos = _isGroupMode - ? _x! + groupAllPadding - : _x! + _padding + _pointerLength; - } - } else { - if (_x! - _labelRect.width - _padding - _pointerLength > - boundaryRect.left) { - _xPos = _isGroupMode - ? (_xPos! - (_labelRect.width / 2) - groupAllPadding) - : _xPos! - _labelRect.width - _padding - _pointerLength; - _isLeft = true; - } else { - _xPos = _isGroupMode - ? _x! + groupAllPadding - : _x! + _padding + _pointerLength; - _isRight = true; - } - } - - if (_isGroupMode && - (_yPos! + _labelRect.height) >= boundaryRect.bottom) { - _yPos = boundaryRect.bottom / 2 - _labelRect.height / 2; - } - - if (_isGroupMode && _yPos! <= boundaryRect.top) { - _yPos = boundaryRect.top; - } - } - } - - _labelRect = - _isGroupMode || tooltipDisplayMode == TrackballDisplayMode.nearestPoint - ? Rect.fromLTWH(_xPos!, _yPos!, _labelRect.width, _labelRect.height) - : Rect.fromLTWH( - _isTransposed - ? tooltipPosition!.tooltipTop[index].toDouble() - : _xPos!, - !_isTransposed - ? tooltipPosition!.tooltipTop[index].toDouble() - : _yPos!, - _labelRect.width, - _labelRect.height); - if (_isGroupMode) { - _drawTooltipBackground( - context, - _labelRect, - _nosePointX, - _nosePointY, - _borderRadius, - _isTop, - _backgroundPath, - _isLeft, - _isRight, - index, - null, - null); - } else { - if (_isTransposed - ? tooltipPosition!.tooltipTop[index] >= boundaryRect.left && - tooltipPosition.tooltipBottom[index] <= boundaryRect.right - : tooltipPosition!.tooltipTop[index] >= boundaryRect.top && - tooltipPosition.tooltipBottom[index] <= boundaryRect.bottom) { - _drawTooltipBackground( - context, - _labelRect, - _nosePointX, - _nosePointY, - _borderRadius, - _isTop, - _backgroundPath, - _isLeft, - _isRight, - index, - (series is RangeSeriesRendererBase) || - (series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - series is CandleSeriesRenderer - ? chartPointInfo[index].highXPosition - : series is BoxAndWhiskerSeriesRenderer - ? chartPointInfo[index].maxXPosition - : chartPointInfo[index].xPosition, - (series is RangeSeriesRendererBase) || - (series is HiloOpenCloseSeriesRenderer || - series is HiloSeriesRenderer) || - series is CandleSeriesRenderer - ? chartPointInfo[index].highYPosition - : series is BoxAndWhiskerSeriesRenderer - ? chartPointInfo[index].maxYPosition - : chartPointInfo[index].yPosition); - } - } - } - - /// To find the trackball tooltip size. - void _calculateTooltipSize( - Rect labelRect, List? chartPointInfo, int index) { - _isTop = true; - _isRight = false; - if (chartPointInfo![index].series != null && - chartPointInfo[index].series is BarSeriesRenderer - ? _isTransposed - : _isTransposed) { - _xPos = _x! - (labelRect.width / 2); - _yPos = (_y! - labelRect.height) - _padding; - _nosePointY = labelRect.top - _padding; - _nosePointX = labelRect.left; - final double tooltipRightEnd = _x! + (labelRect.width / 2); - _xPos = _xPos! < boundaryRect.left - ? boundaryRect.left - : tooltipRightEnd > _totalWidth - ? _totalWidth - labelRect.width - : _xPos; - _yPos = _yPos! - _pointerLength; - if (_yPos! + labelRect.height >= boundaryRect.bottom) { - _yPos = boundaryRect.bottom - labelRect.height; - } - } else { - _xPos = _x; - _yPos = _y! - labelRect.height / 2; - _nosePointY = _yPos!; - _nosePointX = labelRect.left; - if (!_isRtl) { - if (_xPos! + labelRect.width + _padding + _pointerLength > - boundaryRect.right) { - _xPos = _isGroupMode - ? _xPos! - labelRect.width - groupAllPadding - : _xPos! - labelRect.width - _padding - _pointerLength; - _isLeft = true; - } else { - _xPos = _isGroupMode - ? _x! + groupAllPadding - : _x! + _padding + _pointerLength; - _isLeft = false; - _isRight = true; - } - } else { - _xPos = _isGroupMode - ? _xPos! - labelRect.width - groupAllPadding - : _xPos! - labelRect.width - _padding - _pointerLength; - if (_xPos! < boundaryRect.left) { - _xPos = _isGroupMode - ? _x! + groupAllPadding - : _x! + _padding + _pointerLength; - _isRight = true; - } else { - _isLeft = true; - } - } - if (_yPos! + labelRect.height >= boundaryRect.bottom) { - _yPos = boundaryRect.bottom - labelRect.height; - } - } - } - - /// To draw the line for the trackball. - void _drawTrackBallLine(Canvas canvas, Paint paint, int index) { - final Path dashArrayPath = Path(); - if (chartPointInfo[index].series is BarSeriesRenderer - ? _isTransposed - : _isTransposed) { - dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition!); - dashArrayPath.lineTo( - boundaryRect.right, chartPointInfo[index].yPosition!); - } else { - dashArrayPath.moveTo(chartPointInfo[index].xPosition!, boundaryRect.top); - dashArrayPath.lineTo( - chartPointInfo[index].xPosition!, boundaryRect.bottom); - } - lineDashArray != null - ? drawDashes(canvas, lineDashArray, paint, path: dashArrayPath) - : drawDashes(canvas, null, paint, path: dashArrayPath); - } - - /// To draw background of trackball tooltip. - void _drawTooltipBackground( - PaintingContext context, - Rect labelRect, - double xPos, - double yPos, - double borderRadius, - bool isTop, - Path backgroundPath, - bool isLeft, - bool isRight, - int index, - double? xPosition, - double? yPosition) { - final double startArrow = _pointerLength; - final double endArrow = _pointerLength; - if (isTop) { - _drawTooltip( - context, - labelRect, - xPos, - yPos, - xPos - startArrow, - (yPos - startArrow) - 1, - xPos + endArrow, - (yPos - endArrow) - 1, - borderRadius, - backgroundPath, - isLeft, - isRight, - index, - xPosition, - yPosition); - } else { - _drawTooltip( - context, - labelRect, - xPos, - yPos, - xPos - startArrow, - (yPos + startArrow) + 1, - xPos + endArrow, - (yPos + endArrow) + 1, - borderRadius, - backgroundPath, - isLeft, - isRight, - index, - xPosition, - yPosition); - } - } - - /// To draw the tooltip on the trackball. - void _drawTooltip( - PaintingContext context, - Rect tooltipRect, - double? xPos, - double? yPos, - double startX, - double startY, - double endX, - double endY, - double borderRadius, - Path backgroundPath, - bool isLeft, - bool isRight, - int index, - double? xPosition, - double? yPosition) { - backgroundPath.reset(); - if (!_canResetPath && tooltipDisplayMode != TrackballDisplayMode.none) { - if (!_isGroupMode && - !(xPosition == null || yPosition == null) && - parentBox != null && - (tooltipRect.left > boundaryRect.left && - tooltipRect.right < boundaryRect.right && - tooltipRect.top > boundaryRect.top && - tooltipRect.bottom < boundaryRect.bottom)) { - if (_isTransposed) { - if (isLeft) { - startX = tooltipRect.left + borderRadius; - endX = startX + _pointerWidth; - } else if (isRight) { - endX = tooltipRect.right - borderRadius; - startX = endX - _pointerWidth; - } - - backgroundPath.moveTo( - (tooltipRect.left + tooltipRect.width / 2) - _pointerWidth, - startY); - backgroundPath.lineTo(xPosition, yPosition); - backgroundPath.lineTo( - (tooltipRect.right - tooltipRect.width / 2) + _pointerWidth, - endY); - } else { - if (isLeft) { - backgroundPath.moveTo(tooltipRect.right - 1, - tooltipRect.top + tooltipRect.height / 2 - _pointerWidth); - backgroundPath.lineTo(tooltipRect.right - 1, - tooltipRect.bottom - tooltipRect.height / 2 + _pointerWidth); - backgroundPath.lineTo( - tooltipRect.right + _pointerLength, yPosition); - backgroundPath.lineTo(tooltipRect.right - 1, - tooltipRect.top + tooltipRect.height / 2 - _pointerWidth); - } else { - backgroundPath.moveTo(tooltipRect.left + 1, - tooltipRect.top + tooltipRect.height / 2 - _pointerWidth); - backgroundPath.lineTo(tooltipRect.left + 1, - tooltipRect.bottom - tooltipRect.height / 2 + _pointerWidth); - backgroundPath.lineTo(tooltipRect.left - _pointerLength, yPosition); - backgroundPath.lineTo(tooltipRect.left + 1, - tooltipRect.top + tooltipRect.height / 2 - _pointerWidth); - } - } - } - - double rectLeft = tooltipRect.left; - double rectRight = tooltipRect.right; - - if (tooltipRect.width < boundaryRect.width && - tooltipRect.height < boundaryRect.height) { - if (tooltipRect.left < boundaryRect.left) { - rectLeft = rectLeft + (boundaryRect.left - tooltipRect.left); - rectRight = rectRight + (boundaryRect.left - tooltipRect.left); - } else if (tooltipRect.right > boundaryRect.right) { - rectLeft = rectLeft - (tooltipRect.right - boundaryRect.right); - rectRight = rectRight - (tooltipRect.right - boundaryRect.right); - } - tooltipRect = Rect.fromLTRB( - rectLeft, tooltipRect.top, rectRight, tooltipRect.bottom); - if ((tooltipRect.left < boundaryRect.left) || - tooltipRect.right > boundaryRect.right) { - tooltipRect = Rect.zero; - } - - if (tooltipRect != Rect.zero) { - _drawRectAndText(context, backgroundPath, tooltipRect, index); - } - } else { - tooltipRect = Rect.zero; - } - - xPos = null; - yPos = null; - } - } - - TextStyle _createLabelStyle(FontWeight fontWeight, TextStyle labelStyle) { - return TextStyle( - fontWeight: fontWeight, - color: labelStyle.color, - fontSize: labelStyle.fontSize, - fontFamily: labelStyle.fontFamily, - fontStyle: labelStyle.fontStyle, - inherit: labelStyle.inherit, - backgroundColor: labelStyle.backgroundColor, - letterSpacing: labelStyle.letterSpacing, - wordSpacing: labelStyle.wordSpacing, - textBaseline: labelStyle.textBaseline, - height: labelStyle.height, - locale: labelStyle.locale, - foreground: labelStyle.foreground, - background: labelStyle.background, - shadows: labelStyle.shadows, - fontFeatures: labelStyle.fontFeatures, - decoration: labelStyle.decoration, - decorationColor: labelStyle.decorationColor, - decorationStyle: labelStyle.decorationStyle, - decorationThickness: labelStyle.decorationThickness, - debugLabel: labelStyle.debugLabel, - fontFamilyFallback: labelStyle.fontFamilyFallback); - } - - double _multiLineTextOffset(RRect tooltipRect, String text, int index, - double totalLabelWidth, TextStyle style, - [double? previousWidth]) { - final double textOffsetX = - tooltipRect.left + tooltipRect.width / 2 + totalLabelWidth / 2; - final Size currentTextSize = measureText(text, style); - return textOffsetX - - currentTextSize.width - - (index == 0 ? 0 : previousWidth!); - } - - /// Draw trackball tooltip rect and text. - void _drawRectAndText( - PaintingContext context, Path backgroundPath, Rect rect, int index, - // ignore: unused_element - [Path? arrowPath]) { - const double textOffsetPadding = 4; - final RRect tooltipRect = RRect.fromRectAndCorners( - rect, - bottomLeft: Radius.circular(_borderRadius), - bottomRight: Radius.circular(_borderRadius), - topLeft: Radius.circular(_borderRadius), - topRight: Radius.circular(_borderRadius), - ); - const double padding = 10; - - final Paint fillPaint = Paint() - ..color = tooltipSettings.color ?? _chartTheme!.crosshairBackgroundColor - ..isAntiAlias = true - ..style = PaintingStyle.fill; - - if (tooltipSettings.borderWidth > 0 && builder == null) { - final Paint strokePaint = Paint() - ..color = - tooltipSettings.borderColor ?? _chartTheme!.crosshairBackgroundColor - ..strokeWidth = tooltipSettings.borderWidth - ..strokeCap = StrokeCap.butt - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - context.canvas.drawPath(backgroundPath, strokePaint); - context.canvas.drawRRect(tooltipRect, strokePaint); - } - if (builder == null) { - context.canvas.drawRRect(tooltipRect, fillPaint); - context.canvas.drawPath(backgroundPath, fillPaint); - final Paint dividerPaint = Paint(); - dividerPaint.color = _chartTheme!.tooltipSeparatorColor; - dividerPaint.strokeWidth = 1; - dividerPaint.style = PaintingStyle.stroke; - dividerPaint.isAntiAlias = true; - if (_isGroupMode && _divider) { - final Size headerResult = - measureText(_stringValue[0].label, _labelStyle); - context.canvas.drawLine( - Offset(tooltipRect.left + padding, - tooltipRect.top + headerResult.height + padding), - Offset(tooltipRect.right - padding, - tooltipRect.top + headerResult.height + padding), - dividerPaint); - } - } - double eachTextHeight = 0; - Size labelSize; - double totalHeight = 0; - final bool isRtl = _isRtl; - int markerIndex = 0; - - for (int i = 0; i < _stringValue.length; i++) { - labelSize = measureText(_stringValue[i].label, _labelStyle); - totalHeight += labelSize.height; - } - - eachTextHeight = - (tooltipRect.top + tooltipRect.height / 2) - totalHeight / 2; - - for (int i = 0; i < _stringValue.length; i++) { - _markerPadding = 0; - if (tooltipSettings.canShowMarker) { - if (_isGroupMode && i == 0) { - _markerPadding = 0; - } else { - _markerPadding = 10 - _markerSize + 5; - } - } - const double animationFactor = 1; - _labelStyle = _createLabelStyle(FontWeight.normal, _labelStyle); - labelSize = measureText(_stringValue[i].label, _labelStyle); - eachTextHeight += labelSize.height; - if (!_stringValue[i].label.contains(':') && - !_stringValue[i].label.contains('') && - !_stringValue[i].label.contains('')) { - _labelStyle = _createLabelStyle(FontWeight.bold, _labelStyle); - _drawTooltipMarker( - _stringValue[i].label, - context, - tooltipRect, - animationFactor, - labelSize, - _stringValue[i].seriesRenderer ?? chartPointInfo[index].series!, - i, - null, - null, - eachTextHeight, - _isGroupMode ? markerIndex : index); - drawText( - context.canvas, - _stringValue[i].label, - Offset( - (tooltipRect.left + tooltipRect.width / 2) - - labelSize.width / 2 + - (isRtl ? -_markerPadding : _markerPadding), - eachTextHeight - labelSize.height), - _labelStyle); - } else { - // ignore: unnecessary_null_comparison - if (_stringValue[i].label != null) { - final List str = _stringValue[i].label.split('\n'); - double padding = 0; - if (str.length > 1) { - for (int j = 0; j < str.length; j++) { - final List str1 = str[j].split(':'); - if (str1.length > 1) { - for (int k = 0; k < str1.length; k++) { - double width = 0.0; - if (isRtl) { - str1[k] = k == 0 ? ': ${str1[k]}'.trim() : '${str1[k]} '; - width = measureText(str1[0], _labelStyle).width; - } else { - width = - k > 0 ? measureText(str1[k - 1], _labelStyle).width : 0; - str1[k] = k == 1 ? ':${str1[k]}' : str1[k]; - } - _labelStyle = _createLabelStyle( - k > 0 ? FontWeight.bold : FontWeight.normal, _labelStyle); - - if (k == 0) { - _drawTooltipMarker( - str1[k], - context, - tooltipRect, - animationFactor, - labelSize, - _stringValue[i].seriesRenderer, - i, - null, - width, - eachTextHeight, - _isGroupMode ? markerIndex : index); - } - drawText( - context.canvas, - str1[k], - isRtl - ? Offset( - _multiLineTextOffset( - tooltipRect, - str1[k], - k, - labelSize.width, - _labelStyle, - width, - ) - - ((!_isGroupMode && - tooltipSettings.canShowMarker) - ? _markerPadding - : textOffsetPadding), - (eachTextHeight - labelSize.height) + padding) - : Offset( - (((!_isGroupMode && tooltipSettings.canShowMarker) - ? (tooltipRect.left + - tooltipRect.width / 2 - - labelSize.width / 2) - : (tooltipRect.left + - textOffsetPadding)) + - _markerPadding) + - width, - (eachTextHeight - labelSize.height) + padding), - _labelStyle); - - padding = k > 0 - ? padding + - (_labelStyle.fontSize! + - (_labelStyle.fontSize! * 0.15)) - : padding; - } - } else { - _labelStyle = _createLabelStyle(FontWeight.bold, _labelStyle); - - _drawTooltipMarker( - str1[str1.length - 1], - context, - tooltipRect, - animationFactor, - labelSize, - _stringValue[i].seriesRenderer, - i, - null, - null, - eachTextHeight, - _isGroupMode ? markerIndex : index, - measureText(str1[str1.length - 1], _labelStyle)); - _markerPadding = tooltipSettings.canShowMarker - ? _markerPadding + - (j == 0 && !_isGroupMode - ? 13 - : j == 0 && _isGroupMode - ? 7 - : 0) - : 0; - drawText( - context.canvas, - str1[str1.length - 1], - isRtl - ? Offset( - tooltipRect.right - - measureText(str1[str1.length - 1], _labelStyle) - .width - - _markerPadding - - textOffsetPadding, - eachTextHeight - labelSize.height + padding) - : Offset( - _markerPadding + - tooltipRect.left + - textOffsetPadding, - eachTextHeight - labelSize.height + padding), - _labelStyle); - padding = padding + - (_labelStyle.fontSize! + (_labelStyle.fontSize! * 0.15)); - } - } - } else { - final bool hasFormat = tooltipSettings.format != null; - List str1 = str[str.length - 1].split(':'); - final List boldString = []; - if (str[str.length - 1].contains('')) { - str1 = []; - final List boldSplit = str[str.length - 1].split(''); - for (int i = 0; i < boldSplit.length; i++) { - if (boldSplit[i] != '') { - boldString.add(boldSplit[i].substring( - boldSplit[i].indexOf('') + 3, boldSplit[i].length)); - final List str2 = boldSplit[i].split(''); - for (int s = 0; s < str2.length; s++) { - str1.add(str2[s]); - } - } - } - } else if (str1.length > 2 || - _xFormat || - !_isColon || - _headerText) { - str1 = []; - str1.add(str[str.length - 1]); - } - double previousWidth = 0.0; - for (int j = 0; j < str1.length; j++) { - bool isBold = false; - for (int i = 0; i < boldString.length; i++) { - if (str1[j] == boldString[i]) { - isBold = true; - break; - } - } - final double width = - j > 0 ? measureText(str1[j - 1], _labelStyle).width : 0; - previousWidth += width; - String colon = boldString.isNotEmpty - ? '' - : j > 0 - ? ' :' - : ''; - if (isRtl & !hasFormat) { - colon = boldString.isNotEmpty - ? '' - : j > 0 - ? ' : ' - : ''; - } - _labelStyle = _createLabelStyle( - ((_headerText && boldString.isEmpty) || _xFormat || isBold) - ? FontWeight.bold - : j > 0 - ? boldString.isNotEmpty - ? FontWeight.normal - : FontWeight.bold - : FontWeight.normal, - _labelStyle); - - if (j == 0) { - _drawTooltipMarker( - str1[j], - context, - tooltipRect, - animationFactor, - labelSize, - _stringValue[i].seriesRenderer, - i, - previousWidth, - width, - eachTextHeight, - _isGroupMode ? markerIndex : index); - } - _markerPadding = tooltipSettings.canShowMarker - ? _markerPadding + - (j == 0 && !_isGroupMode - ? 13 - : j == 0 && _isGroupMode - ? 7 - : 0) - : 0; - final Offset textEndPoint = Offset( - isRtl - ? tooltipRect.right - textOffsetPadding - _markerPadding - : _markerPadding + - (tooltipRect.left + textOffsetPadding) + - (previousWidth > width ? previousWidth : width), - eachTextHeight - labelSize.height); - - if (!isRtl || (isRtl && !hasFormat)) { - drawText( - context.canvas, - isRtl ? str1[j] + colon : colon + str1[j], - isRtl - ? Offset( - textEndPoint.dx - - (previousWidth > width - ? previousWidth - : width) - - measureText(str1[j] + colon, _labelStyle).width, - textEndPoint.dy) - : textEndPoint, - _labelStyle); - } else { - _drawTextWithFormat(context, str1, _labelStyle, textEndPoint); - break; - } - _headerText = false; - } - } - } - } - if (_isGroupMode && i != 0 && _stringValue[i].label != '') { - markerIndex++; - } - } - } - - void _drawTextWithFormat(PaintingContext context, List textCollection, - TextStyle labelStyle, Offset textEndPoint) { - final String arrangedText = _rtlStringWithColon(textCollection); - final List strings = arrangedText.split(':'); - double previousWidth = 0.0; - for (int textCount = 0; textCount < strings.length; textCount++) { - final bool containsBackSpace = strings[textCount].endsWith(' '); - final bool containsFrontSpace = strings[textCount].startsWith(' '); - strings[textCount] = strings[textCount].trim(); - if (containsFrontSpace) { - strings[textCount] = '${strings[textCount]} '; - } - if (containsBackSpace) { - strings[textCount] = ' ${strings[textCount]}'; - } - final String currentText = - textCount > 0 ? '${strings[textCount]}:' : (strings[textCount]); - final Size currentTextSize = measureText(currentText, labelStyle); - previousWidth += currentTextSize.width; - drawText(context.canvas, currentText, - Offset(textEndPoint.dx - previousWidth, textEndPoint.dy), labelStyle); - } - } - - String _rtlStringWithColon(List textCollection) { - String string = ''; - for (int i = textCollection.length - 1; i >= 0; i--) { - final bool containsBackSpace = textCollection[i].endsWith(' '); - final bool containsFrontSpace = textCollection[i].startsWith(' '); - textCollection[i] = textCollection[i].trim(); - if (containsFrontSpace) { - textCollection[i] = '${textCollection[i]} '; - } - if (containsBackSpace) { - textCollection[i] = ' ${textCollection[i]}'; - } - string = string + - (i == textCollection.length - 1 - ? textCollection[i] - : ':${textCollection[i]}'); - } - return string; - } - - /// Draw marker inside the trackball tooltip. - void _drawTooltipMarker( - String labelValue, - PaintingContext context, - RRect tooltipRect, - double animationFactor, - Size tooltipMarkerResult, - dynamic seriesRenderer, - int i, - double? previousWidth, - double? width, - double eachTextHeight, - int index, - [Size? headerSize]) { - final Size tooltipStringResult = tooltipMarkerResult; - _markerSize = 5; - Offset markerPoint; - final bool isRtl = _isRtl; - if (tooltipSettings.canShowMarker) { - if (!_isGroupMode) { - if (seriesRenderer is HiloOpenCloseSeriesRenderer || - seriesRenderer is HiloSeriesRenderer || - seriesRenderer is CandleSeriesRenderer || - seriesRenderer is BoxAndWhiskerSeriesRenderer) { - final double markerStartPoint = _isRtl - ? tooltipStringResult.width / 2 + _markerSize - : -tooltipMarkerResult.width / 2 - _markerSize; - markerPoint = Offset( - tooltipRect.left + tooltipRect.width / 2 + markerStartPoint, - (eachTextHeight - tooltipStringResult.height / 2) + 0.0); - _renderMarker(markerPoint, seriesRenderer, animationFactor, - context.canvas, index); - } else { - final double markerStartPoint = isRtl - ? tooltipStringResult.width / 2 + _markerSize - : -tooltipMarkerResult.width / 2 - _markerSize; - markerPoint = Offset( - tooltipRect.left + tooltipRect.width / 2 + markerStartPoint, - ((tooltipRect.top + tooltipRect.height) - - tooltipStringResult.height / 2) - - _markerSize); - } - _renderMarker(markerPoint, seriesRenderer, animationFactor, - context.canvas, index); - } else { - if (i > 0 && labelValue != '') { - if (tooltipSettings.format == null && - (seriesRenderer is IndicatorRenderer || - seriesRenderer.name != seriesRenderer.localizedName())) { - if (previousWidth != null && width != null) { - final double markerStartPoint = - isRtl ? (tooltipRect.right - 10) : (tooltipRect.left + 10); - markerPoint = Offset( - markerStartPoint + - (previousWidth > width ? previousWidth : width), - eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker(markerPoint, seriesRenderer, animationFactor, - context.canvas, index); - } else if (_stringValue[i].label != '' && headerSize != null) { - markerPoint = Offset( - isRtl ? tooltipRect.right - 10 : tooltipRect.left + 10, - (headerSize.height * 2 + - tooltipRect.top + - _markerSize + - headerSize.height / 2) + - (i == 1 - ? 0 - : _lastMarkerResultHeight - headerSize.height)); - _lastMarkerResultHeight = tooltipMarkerResult.height; - _stringValue[i].needRender = false; - _renderMarker(markerPoint, seriesRenderer, animationFactor, - context.canvas, index); - } - } else { - final double tooltipCenter = - tooltipRect.left + tooltipRect.width / 2; - final double markerStartPoint = isRtl - ? _stringValue[i].label.contains(':') - ? tooltipRect.right - _markerPadding / 2 - _markerSize - : tooltipCenter + - (tooltipStringResult.width / 2 + _markerSize) - : _stringValue[i].label.contains(':') - ? tooltipRect.left + _markerPadding / 2 + _markerSize - : tooltipCenter + - (-tooltipMarkerResult.width / 2 - _markerSize); - markerPoint = Offset(markerStartPoint, - eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker(markerPoint, seriesRenderer, animationFactor, - context.canvas, index); - } - } - } - } - } - - // To render marker for the chart tooltip. - void _renderMarker(Offset markerPoint, dynamic seriesRenderer, - double animationFactor, Canvas canvas, int index) { - final MarkerSettings settings = markerSettings == null && - seriesRenderer is CartesianSeriesRenderer - ? seriesRenderer.markerSettings - : markerSettings ?? const TrackballMarkerSettings(); - final Path markerPath = _markerShapesPath( - settings.shape, - markerPoint, - Size((2 * _markerSize) * animationFactor, - (2 * _markerSize) * animationFactor), - seriesRenderer, - index); - - if (settings.shape == DataMarkerType.image) { - _drawImageMarker(seriesRenderer, canvas, markerPoint.dx, markerPoint.dy); - } - - Color? seriesColor; - if (seriesRenderer is CandleSeriesRenderer) { - seriesColor = seriesRenderer.color ?? - (seriesRenderer.enableSolidCandles - ? seriesRenderer - .segments[chartPointInfo[0].dataPointIndex!].fillPaint.color - : seriesRenderer.segments[chartPointInfo[0].dataPointIndex!] - .strokePaint.color); - } else { - seriesColor = seriesRenderer is CartesianSeriesRenderer - ? seriesRenderer.color ?? - seriesRenderer - .segments[chartPointInfo[0].dataPointIndex!].fillPaint.color - : chartPointInfo[index].color!; - } - - Paint markerPaint = Paint(); - markerPaint.color = settings.color ?? seriesColor; - markerPaint.isAntiAlias = true; - if (seriesRenderer is CartesianSeriesRenderer && - seriesRenderer.gradient != null) { - markerPaint = _linearGradientPaint( - seriesRenderer.gradient!, - _markerShapesPath( - settings.shape, - Offset(markerPoint.dx, markerPoint.dy), - Size((2 * _markerSize) * animationFactor, - (2 * _markerSize) * animationFactor), - seriesRenderer) - .getBounds(), - seriesRenderer.xAxis!.isInversed); - } - canvas.drawPath(markerPath, markerPaint); - Paint markerBorderPaint = Paint(); - markerBorderPaint.color = settings.borderColor ?? seriesColor; - markerBorderPaint.strokeWidth = settings.borderWidth; - markerBorderPaint.style = PaintingStyle.stroke; - markerBorderPaint.isAntiAlias = true; - - if (seriesRenderer is CartesianSeriesRenderer && - seriesRenderer.gradient != null) { - markerBorderPaint = _linearGradientPaint( - seriesRenderer.gradient!, - _markerShapesPath( - settings.shape, - Offset(markerPoint.dx, markerPoint.dy), - Size((2 * _markerSize) * animationFactor, - (2 * _markerSize) * animationFactor), - seriesRenderer) - .getBounds(), - seriesRenderer.xAxis!.isInversed); - } - canvas.drawPath(markerPath, markerBorderPaint); - } - - /// Paint the image marker. - void _drawImageMarker( - dynamic series, Canvas canvas, double pointX, double pointY) { - if (_image != null) { - final double imageWidth = 2 * markerSettings!.width; - final double imageHeight = 2 * markerSettings!.height; - final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, - pointY - imageHeight / 2, imageWidth, imageHeight); - paintImage( - canvas: canvas, rect: positionRect, image: _image!, fit: BoxFit.fill); - } - } - - /// Get gradient fill colors. - Paint _linearGradientPaint( - LinearGradient gradientFill, Rect region, bool isInvertedAxis) { - Paint gradientPaint; - gradientPaint = Paint() - ..shader = gradientFill.createShader(region) - ..style = PaintingStyle.fill - ..isAntiAlias = true; - return gradientPaint; - } - - /// Returns the path of marker shapes. - Path _markerShapesPath(DataMarkerType markerType, Offset position, Size size, - [dynamic seriesRenderer, - int? index, - // ignore: unused_element - CurvedAnimation? animationController, - ChartSegment? segment]) { - final Path path = Path(); - final Rect rect = segment != null && segment is ScatterSegment - ? Rect.fromLTWH( - position.dx - ((segment.animationFactor * size.width) / 2), - position.dy - ((segment.animationFactor * size.height) / 2), - segment.animationFactor * size.width, - segment.animationFactor * size.height) - : Rect.fromLTWH(position.dx - size.width / 2, - position.dy - size.height / 2, size.width, size.height); - switch (markerType) { - case DataMarkerType.circle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.circle); - } - break; - case DataMarkerType.rectangle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.rectangle); - } - break; - case DataMarkerType.image: - { - if (seriesRenderer != null) { - _loadMarkerImage(seriesRenderer); - } - } - break; - case DataMarkerType.pentagon: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.pentagon); - } - break; - case DataMarkerType.verticalLine: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine); - } - break; - case DataMarkerType.invertedTriangle: - { - getShapesPath( - path: path, - rect: rect, - shapeType: ShapeMarkerType.invertedTriangle); - } - break; - case DataMarkerType.horizontalLine: - { - getShapesPath( - path: path, - rect: rect, - shapeType: ShapeMarkerType.horizontalLine); - } - break; - case DataMarkerType.diamond: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.diamond); - } - break; - case DataMarkerType.triangle: - { - getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.triangle); - } - break; - case DataMarkerType.none: - break; - } - return path; - } - - // ignore: avoid_void_async - void _loadMarkerImage(CartesianSeriesRenderer series) async { - if ((markerSettings != null && - markerSettings!.shape == DataMarkerType.image && - markerSettings!.image != null) || - // ignore: unnecessary_null_comparison - (series.markerSettings != null && - (series.markerSettings.isVisible || - series is ScatterSeriesRenderer) && - series.markerSettings.shape == DataMarkerType.image && - series.markerSettings.image != null)) { - _calculateImage(); - } - } - - /// below method is for getting image from the image provider - Future _imageInfo(ImageProvider imageProvider) async { - final Completer completer = Completer(); - imageProvider - .resolve(ImageConfiguration.empty) - .addListener(ImageStreamListener((ImageInfo info, bool _) { - completer.complete(info); - // return completer.future; - })); - final ImageInfo imageInfo = await completer.future; - return imageInfo.image; - } - - /// Method to calculate the image value -//ignore: avoid_void_async - void _calculateImage() async { - if (markerSettings!.image != null) { - _image = await _imageInfo(markerSettings!.image!); - if (parentBox != null) { - (parentBox! as RenderBehaviorArea).invalidate(); - } - } - } - - /// To get the label format of the date-time axis. - DateFormat _dateTimeLabelFormat(RenderChartAxis axis, - [int? interval, int? prevInterval]) { - DateFormat? format; - final bool notDoubleInterval = - (axis.interval != null && axis.interval! % 1 == 0) || - axis.interval == null; - DateTimeIntervalType? actualIntervalType; - num? minimum; - minimum = axis.visibleRange!.minimum; - actualIntervalType = (axis as RenderDateTimeAxis).visibleIntervalType; - switch (actualIntervalType) { - case DateTimeIntervalType.years: - format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.months: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - - break; - case DateTimeIntervalType.days: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - break; - case DateTimeIntervalType.hours: - format = DateFormat.j(); - break; - case DateTimeIntervalType.minutes: - format = DateFormat.Hm(); - break; - case DateTimeIntervalType.seconds: - format = DateFormat.ms(); - break; - case DateTimeIntervalType.milliseconds: - final DateFormat dateFormat = DateFormat('ss.SSS'); - format = dateFormat; - break; - case DateTimeIntervalType.auto: - break; - // ignore: no_default_cases - default: - break; - } - return format!; - } - - /// Gets the the actual label value for tooltip and data label etc. - String _labelValue(num value, dynamic axis, [int? showDigits]) { - if (value.toString().split('.').length > 1) { - final String str = value.toString(); - final List list = str.split('.'); - value = double.parse(value.toStringAsFixed(showDigits ?? 3)); - value = (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000' || - list[1] == '0000000') - ? value.round() - : value; - } - final dynamic text = axis is RenderNumericAxis && axis.numberFormat != null - ? axis.numberFormat!.format(value) - : value; - return ((axis.labelFormat != null && axis.labelFormat != '') - ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString()) - : text.toString()) as String; - } - - /// Calculate the dateTime format. - DateFormat? _dateTimeFormat(DateTimeIntervalType? actualIntervalType, - int? interval, int? prevInterval) { - final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); - final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); - DateFormat? format; - final bool isIntervalDecimal = interval % 1 == 0; - if (actualIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; - } - - /// Returns the first label format for date time values. - DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { - DateFormat? format; - - if (actualIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (actualIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - - return format; - } - - /// Find logarithmic values. - num _calculateLogBaseValue(num value, num base) => log(value) / log(base); -} - -/// Render the annotation widget in the respective position. -class TrackballTemplateRenderBox extends RenderShiftedBox { - /// Creates an instance of trackball template render box. - TrackballTemplateRenderBox( - this._template, this.xPos, this.yPos, this.trackballBehavior, - [this.chartPointInfo, this.index, RenderBox? child]) - : super(child); - - Widget _template; - - /// Holds the value of x and y position. - double xPos, yPos; - - /// Specifies the list of chart point info. - List? chartPointInfo; - - /// Holds the value of index. - int? index; - - /// Holds the value of pointer length and pointer width respectively. - late double pointerLength, pointerWidth; - - /// Holds the value of trackball template rect. - Rect? trackballTemplateRect; - - /// Holds the value of boundary rect. - late Rect boundaryRect; - - /// Specifies the value of padding. - num padding = 10; - - /// Specifies the value of trackball behavior. - TrackballBehavior trackballBehavior; - - /// Specifies whether to group all the points. - bool isGroupAllPoints = false; - - /// Specifies whether it is the nearest point. - bool isNearestPoint = false; - - /// Gets the template widget. - Widget get template => _template; - - /// Specifies whether tooltip is present at right. - bool isRight = false; - - /// Specifies whether tooltip is present at bottom. - bool isBottom = false; - - /// Specifies whether the template is present inside the bounds. - bool isTemplateInBounds = true; - // Offset arrowOffset; - - /// Holds the tooltip position. - TooltipPositions? tooltipPosition; - - /// Holds the value of box parent data. - late BoxParentData childParentData; - - /// Sets the template value. - set template(Widget value) { - if (_template != value) { - _template = value; - markNeedsLayout(); - } - } - - bool isTransposed = false; - - @override - void performLayout() { - isTransposed = chartPointInfo != null && - chartPointInfo!.isNotEmpty && - chartPointInfo![0].series!.isTransposed; - isGroupAllPoints = trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.groupAllPoints; - isNearestPoint = trackballBehavior.tooltipDisplayMode == - TrackballDisplayMode.nearestPoint; - final List? tooltipTop = []; - final List tooltipBottom = []; - final List visiblePoints = trackballBehavior.visiblePoints; - final List xAxesInfo = trackballBehavior.xAxesInfo; - final List yAxesInfo = trackballBehavior.yAxesInfo; - boundaryRect = trackballBehavior.boundaryRect; - final num totalWidth = boundaryRect.left + boundaryRect.width; - tooltipPosition = trackballBehavior.tooltipPosition; - final bool isTrackballMarkerEnabled = - trackballBehavior.markerSettings != null; - final BoxConstraints constraints = this.constraints; - pointerLength = trackballBehavior.tooltipSettings.arrowLength; - pointerWidth = trackballBehavior.tooltipSettings.arrowWidth; - double left, top; - Offset? offset; - if (child != null) { - child!.layout(constraints, parentUsesSize: true); - size = constraints.constrain(Size(child!.size.width, child!.size.height)); - if (child!.parentData is BoxParentData) { - childParentData = child!.parentData as BoxParentData; - - if (isGroupAllPoints) { - if (trackballBehavior.tooltipAlignment == ChartAlignment.center) { - yPos = boundaryRect.center.dy - size.height / 2; - } else if (trackballBehavior.tooltipAlignment == - ChartAlignment.near) { - yPos = boundaryRect.top; - } else { - yPos = boundaryRect.bottom; - } - - if (yPos + size.height > boundaryRect.bottom && - trackballBehavior.tooltipAlignment == ChartAlignment.far) { - yPos = boundaryRect.bottom - size.height; - } - } - if (chartPointInfo != null && !isGroupAllPoints) { - for (int index = 0; index < chartPointInfo!.length; index++) { - tooltipTop!.add(isTransposed - ? visiblePoints[index].closestPointX - (size.width / 2) - : visiblePoints[index].closestPointY - size.height / 2); - tooltipBottom.add(isTransposed - ? visiblePoints[index].closestPointX + (size.width / 2) - : visiblePoints[index].closestPointY + size.height / 2); - xAxesInfo.add(chartPointInfo![index].series!.xAxis!); - yAxesInfo.add(chartPointInfo![index].series!.yAxis!); - } - } - if (tooltipTop != null && tooltipTop.isNotEmpty) { - tooltipPosition = trackballBehavior._smartTooltipPositions( - tooltipTop, - tooltipBottom, - xAxesInfo, - yAxesInfo, - chartPointInfo!, - isTransposed); - } - - if (!isGroupAllPoints) { - left = (isTransposed - ? tooltipPosition!.tooltipTop[index!] - : xPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0)) - .toDouble(); - top = (isTransposed - ? yPos + - pointerLength + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0) - : tooltipPosition!.tooltipTop[index!]) - .toDouble(); - - if (isNearestPoint) { - left = isTransposed - ? xPos + size.width / 2 - : xPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0); - top = isTransposed - ? yPos + - padding + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0) - : yPos - size.height / 2; - } - - if (!isTransposed) { - if (left + size.width > totalWidth) { - isRight = true; - left = xPos - - size.width - - pointerLength - - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings!.width / 2) - : 0); - } else { - isRight = false; - } - } else { - if (top + size.height > boundaryRect.bottom) { - isBottom = true; - top = yPos - - pointerLength - - size.height - - (isTrackballMarkerEnabled - ? (trackballBehavior.markerSettings!.height) - : 0); - } else { - isBottom = false; - } - } - trackballTemplateRect = - Rect.fromLTWH(left, top, size.width, size.height); - double xPlotOffset = visiblePoints.first.closestPointX - - trackballTemplateRect!.width / 2; - final double rightTemplateEnd = - xPlotOffset + trackballTemplateRect!.width; - final double leftTemplateEnd = xPlotOffset; - - if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect!, offset)) { - isTemplateInBounds = true; - childParentData.offset = Offset(left, top); - } else if (boundaryRect.width > trackballTemplateRect!.width && - boundaryRect.height > trackballTemplateRect!.height) { - isTemplateInBounds = true; - if (rightTemplateEnd > boundaryRect.right) { - xPlotOffset = - xPlotOffset - (rightTemplateEnd - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } else if (leftTemplateEnd < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - leftTemplateEnd); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - isTemplateInBounds = false; - } - } - } - childParentData.offset = Offset(xPlotOffset, yPos); - } else { - child!.layout(constraints.copyWith(maxWidth: 0), - parentUsesSize: true); - isTemplateInBounds = false; - } - } else { - if (xPos + size.width > totalWidth) { - xPos = xPos - - size.width - - 2 * padding - - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0); - } - - trackballTemplateRect = - Rect.fromLTWH(xPos, yPos, size.width, size.height); - double xPlotOffset = visiblePoints.first.closestPointX - - trackballTemplateRect!.width / 2; - final double rightTemplateEnd = - xPlotOffset + trackballTemplateRect!.width; - final double leftTemplateEnd = xPlotOffset; - - if (_isTemplateWithinBounds( - boundaryRect, trackballTemplateRect!, offset) && - (boundaryRect.right > trackballTemplateRect!.right && - boundaryRect.left < trackballTemplateRect!.left)) { - isTemplateInBounds = true; - childParentData.offset = Offset( - xPos + - (trackballTemplateRect!.right + padding > boundaryRect.right - ? trackballTemplateRect!.right + - padding - - boundaryRect.right - : padding) + - (isTrackballMarkerEnabled - ? trackballBehavior.markerSettings!.width / 2 - : 0), - yPos); - } else if (boundaryRect.width > trackballTemplateRect!.width && - boundaryRect.height > trackballTemplateRect!.height) { - isTemplateInBounds = true; - if (rightTemplateEnd > boundaryRect.right) { - xPlotOffset = - xPlotOffset - (rightTemplateEnd - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (totalWidth + - trackballTemplateRect!.width - - boundaryRect.right); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } else if (leftTemplateEnd < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - leftTemplateEnd); - if (xPlotOffset + trackballTemplateRect!.width > - boundaryRect.right) { - xPlotOffset = xPlotOffset - - (xPlotOffset + - trackballTemplateRect!.width - - boundaryRect.right); - if (xPlotOffset < boundaryRect.left) { - xPlotOffset = xPlotOffset + (boundaryRect.left - xPlotOffset); - } - if (xPlotOffset < boundaryRect.left || - xPlotOffset > boundaryRect.right) { - isTemplateInBounds = false; - } - } - } - childParentData.offset = Offset(xPlotOffset, yPos); - } else { - child!.layout(constraints.copyWith(maxWidth: 0), - parentUsesSize: true); - isTemplateInBounds = false; - } - } - } - } else { - size = Size.zero; - } - if (!isGroupAllPoints && index == chartPointInfo!.length - 1) { - tooltipTop?.clear(); - tooltipBottom.clear(); - yAxesInfo.clear(); - xAxesInfo.clear(); - } - } - - /// To check template is within bounds. - bool _isTemplateWithinBounds(Rect bounds, Rect templateRect, Offset? offset) { - final Rect rect = Rect.fromLTWH( - padding + templateRect.left, - (3 * padding) + templateRect.top, - templateRect.width, - templateRect.height); - final Rect axisBounds = Rect.fromLTWH(padding + bounds.left, - (3 * padding) + bounds.top, bounds.width, bounds.height); - return rect.left >= axisBounds.left && - rect.left + rect.width <= axisBounds.left + axisBounds.width && - rect.top >= axisBounds.top && - rect.bottom <= axisBounds.top + axisBounds.height; - } - - @override - void paint(PaintingContext context, Offset offset) { - if (isGroupAllPoints) { - for (int i = 0; i < chartPointInfo!.length; i++) { - trackballBehavior._renderEachMarkers(context.canvas, i); - } - } else { - trackballBehavior._renderEachMarkers(context.canvas, index!); - } - final bool isTemplateWithInBoundsInTransposedChart = - _isTemplateWithinBounds(boundaryRect, trackballTemplateRect!, offset); - if ((!isTransposed && isTemplateInBounds) || - (isTransposed && isTemplateWithInBoundsInTransposedChart)) { - super.paint(context, offset); - } - - if (!isGroupAllPoints) { - final Rect templateRect = Rect.fromLTWH( - offset.dx + trackballTemplateRect!.left, - offset.dy + trackballTemplateRect!.top, - trackballTemplateRect!.width, - trackballTemplateRect!.height); - final Paint fillPaint = Paint() - ..color = trackballBehavior.tooltipSettings.color ?? - (chartPointInfo![index!].series is IndicatorRenderer - ? chartPointInfo![index!].color - : (chartPointInfo![index!].series!.color) ?? - trackballBehavior._chartTheme!.crosshairBackgroundColor) - ..isAntiAlias = true - ..style = PaintingStyle.fill; - final Paint strokePaint = Paint() - ..color = trackballBehavior.tooltipSettings.borderColor ?? - (chartPointInfo![index!].series is IndicatorRenderer - ? chartPointInfo![index!].color - : (chartPointInfo![index!].series!.color) ?? - trackballBehavior._chartTheme!.crosshairBackgroundColor) - ..strokeWidth = trackballBehavior.tooltipSettings.borderWidth - ..strokeCap = StrokeCap.butt - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - final Path path = Path(); - if (trackballTemplateRect!.left > boundaryRect.left && - trackballTemplateRect!.right < boundaryRect.right) { - if (!isTransposed) { - if (!isRight) { - path.moveTo(templateRect.left, - templateRect.top + templateRect.height / 2 - pointerWidth); - path.lineTo(templateRect.left, - templateRect.bottom - templateRect.height / 2 + pointerWidth); - path.lineTo(templateRect.left - pointerLength, yPos + offset.dy); - path.lineTo(templateRect.left, - templateRect.top + templateRect.height / 2 - pointerWidth); - } else { - path.moveTo(templateRect.right, - templateRect.top + templateRect.height / 2 - pointerWidth); - path.lineTo(templateRect.right, - templateRect.bottom - templateRect.height / 2 + pointerWidth); - path.lineTo(templateRect.right + pointerLength, yPos + offset.dy); - path.lineTo(templateRect.right, - templateRect.top + templateRect.height / 2 - pointerWidth); - } - } else if (isTemplateInBounds && - isTemplateWithInBoundsInTransposedChart) { - if (!isBottom) { - path.moveTo( - templateRect.left + templateRect.width / 2 + pointerWidth, - templateRect.top); - path.lineTo( - templateRect.right - templateRect.width / 2 - pointerWidth, - templateRect.top); - path.lineTo(xPos + offset.dx, yPos + offset.dy); - } else { - path.moveTo( - templateRect.left + templateRect.width / 2 + pointerWidth, - templateRect.bottom); - path.lineTo( - templateRect.right - templateRect.width / 2 - pointerWidth, - templateRect.bottom); - path.lineTo(xPos + offset.dx, yPos + offset.dy); - } - } - - if (isTemplateInBounds) { - context.canvas.drawPath(path, fillPaint); - context.canvas.drawPath(path, strokePaint); - } - } - } - } -} - -/// This class has the properties of the crosshair behavior. -/// -/// Crosshair behavior has the activation mode and line type property to set -/// the behavior of the crosshair. It also has the property to customize -/// the appearance. -/// -/// Provide options for activation mode, line type, line color, line width, -/// hide delay for customizing the behavior of the crosshair. -class CrosshairBehavior extends ChartBehavior { - /// Creating an argument constructor of [CrosshairBehavior] class. - CrosshairBehavior({ - this.activationMode = ActivationMode.longPress, - this.lineType = CrosshairLineType.both, - this.lineDashArray, - this.enable = false, - this.lineColor, - this.lineWidth = 1, - this.shouldAlwaysShow = false, - this.hideDelay = 0, - }); - - /// Toggles the visibility of the crosshair. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior(enable: true); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final bool enable; - - /// Width of the crosshair line. - /// - /// Defaults to `1`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineWidth: 5 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final double lineWidth; - - /// Color of the crosshair line. - /// - /// Color will be applied based on the brightness - /// property of the app. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true,lineColor: Colors.red - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final Color? lineColor; - - /// Dashes of the crosshair line. - /// - /// Any number of values can be provided in the list. - /// Odd value is considered as rendering size and even value is - /// considered as gap. - /// - /// Defaults to `[0,0]`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineDashArray: [10,10] - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final List? lineDashArray; - - /// Gesture for activating the crosshair. - /// - /// Crosshair can be activated in tap, double tap - /// and long press. - /// - /// Defaults to `ActivationMode.longPress`. - /// - /// Also refer [ActivationMode]. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// activationMode: ActivationMode.doubleTap - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final ActivationMode activationMode; - - /// Type of crosshair line. - /// - /// By default, both vertical and horizontal lines will be - /// displayed. You can change this by specifying values to this property. - /// - /// Defaults to `CrosshairLineType.both`. - /// - /// Also refer [CrosshairLineType]. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// lineType: CrosshairLineType.horizontal - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final CrosshairLineType lineType; - - /// Enables or disables the crosshair. - /// - /// By default, the crosshair will be hidden on touch. - /// To avoid this, set this property to true. - /// - /// Defaults to `false`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// shouldAlwaysShow: true - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final bool shouldAlwaysShow; - - /// Time delay for hiding the crosshair. - /// - /// Defaults to `0`. - /// - /// ```dart - /// late CrosshairBehavior _crosshairBehavior; - /// - /// void initState() { - /// _crosshairBehavior = CrosshairBehavior( - /// enable: true, - /// hideDelay: 3000 - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// crosshairBehavior: _crosshairBehavior - /// ); - /// } - /// ``` - final double hideDelay; - - /// Hold crosshair target position. - Offset? _position; - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is CrosshairBehavior && - other.activationMode == activationMode && - other.lineType == lineType && - other.lineDashArray == lineDashArray && - other.enable == enable && - other.lineColor == lineColor && - other.lineWidth == lineWidth && - other.shouldAlwaysShow == shouldAlwaysShow && - other.hideDelay == hideDelay; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [ - activationMode, - lineType, - lineDashArray, - enable, - lineColor, - lineWidth, - shouldAlwaysShow, - hideDelay - ]; - return Object.hashAll(values); - } - - /// Displays the crosshair at the specified x and y-positions. - /// - /// x & y - x and y values/pixel where the crosshair needs to be shown. - /// - /// coordinateUnit - specify the type of x and y values given. `pixel` or - /// `point` for logical pixel and chart data point respectively. - /// - /// Defaults to `point`. - void show(dynamic x, double y, [String coordinateUnit = 'point']) { - assert(x != null); - assert(!y.isNaN); - if (coordinateUnit == 'point') { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null) { - _position = rawValueToPixelPoint( - x, y, parent.xAxis, parent.yAxis, parent.isTransposed); - } - } else if (coordinateUnit == 'pixel') { - _position = Offset(x.toDouble(), y); - } - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - - /// Displays the crosshair at the specified point index. - /// - /// pointIndex - index of point at which the crosshair needs to be shown. - void showByIndex(int pointIndex) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent != null && parent.plotArea != null) { - final XyDataSeriesRenderer? seriesRenderer = - parent.plotArea!.firstChild as XyDataSeriesRenderer?; - if (seriesRenderer != null) { - final List visibleIndexes = seriesRenderer.visibleIndexes; - if (visibleIndexes.first <= pointIndex && - pointIndex <= visibleIndexes.last) { - show(seriesRenderer.xRawValues[pointIndex], - seriesRenderer.yValues[pointIndex].toDouble()); - } - } - } - } - - /// Hides the crosshair if it is displayed. - void hide() { - _position = null; - (parentBox as RenderBehaviorArea?)?.invalidate(); - } - - void _onPaint( - PaintingContext context, - Offset offset, - RenderBox parentBox, - SfChartThemeData chartThemeData, - RenderCartesianAxes? cartesianAxes, - ) { - if (_position == null) { - return; - } - - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - - final RenderCartesianAxes? cartesianAxes = parent.cartesianAxes; - if (cartesianAxes == null) { - return; - } - - final Rect plotAreaBounds = parent.paintBounds; - if (plotAreaBounds.contains(offset)) { - _drawLines(context, offset, lineType, parentBox, chartThemeData); - - final Paint fillPaint = Paint()..isAntiAlias = true; - final Paint strokePaint = Paint() - ..isAntiAlias = true - ..style = PaintingStyle.stroke; - - final Offset plotAreaOffset = - (parent.parentData! as BoxParentData).offset; - RenderChartAxis? child = cartesianAxes.firstChild; - while (child != null) { - if (child.interactiveTooltip.enable) { - final TextStyle textStyle = child.chartThemeData!.crosshairTextStyle! - .merge(child.interactiveTooltip.textStyle); - fillPaint.color = child.interactiveTooltip.color ?? - chartThemeData.crosshairBackgroundColor; - strokePaint - ..color = child.interactiveTooltip.borderColor ?? - chartThemeData.crosshairBackgroundColor - ..strokeWidth = child.interactiveTooltip.borderWidth; - - if (child.isVertical) { - if (child.opposedPosition) { - _drawRightAxisTooltip(context.canvas, offset, strokePaint, - fillPaint, plotAreaBounds, child, textStyle, plotAreaOffset); - } else { - _drawLeftAxisTooltip(context.canvas, offset, strokePaint, - fillPaint, plotAreaBounds, child, textStyle, plotAreaOffset); - } - } else { - if (child.opposedPosition) { - _drawTopAxisTooltip(context.canvas, offset, strokePaint, - fillPaint, plotAreaBounds, child, textStyle, plotAreaOffset); - } else { - _drawBottomAxisTooltip(context.canvas, offset, strokePaint, - fillPaint, plotAreaBounds, child, textStyle, plotAreaOffset); - } - } - } - - final CartesianAxesParentData childParentData = - child.parentData! as CartesianAxesParentData; - child = childParentData.nextSibling; - } - } - } - - void _drawLines( - PaintingContext context, - Offset offset, - CrosshairLineType lineType, - RenderBox parentBox, - SfChartThemeData chartThemeData, - ) { - final Paint paint = Paint() - ..isAntiAlias = true - ..color = lineColor ?? chartThemeData.crosshairLineColor - ..strokeWidth = lineWidth - ..style = PaintingStyle.stroke; - - final Rect bounds = parentBox.paintBounds; - switch (lineType) { - case CrosshairLineType.both: - _drawLines(context, offset, CrosshairLineType.horizontal, parentBox, - chartThemeData); - _drawLines(context, offset, CrosshairLineType.vertical, parentBox, - chartThemeData); - break; - - case CrosshairLineType.horizontal: - final Offset start = Offset(bounds.left, offset.dy); - final Offset end = Offset(bounds.right, offset.dy); - drawDashes(context.canvas, lineDashArray, paint, - start: start, end: end); - break; - - case CrosshairLineType.vertical: - final Offset start = Offset(offset.dx, bounds.top); - final Offset end = Offset(offset.dx, bounds.bottom); - drawDashes(context.canvas, lineDashArray, paint, - start: start, end: end); - break; - - case CrosshairLineType.none: - break; - } - } - - /// Draw left axis tooltip. - void _drawLeftAxisTooltip( - Canvas canvas, - Offset position, - Paint strokePaint, - Paint fillPaint, - Rect plotAreaBounds, - RenderChartAxis? cartesianAxis, - TextStyle textStyle, - Offset plotAreaOffset) { - final InteractiveTooltip interactiveTooltip = - cartesianAxis!.interactiveTooltip; - final Offset parentDataOffset = - (cartesianAxis.parentData! as BoxParentData).offset; - final Offset axisOffset = - parentDataOffset.translate(-plotAreaOffset.dx, -plotAreaOffset.dy); - final Rect axisRect = axisOffset & cartesianAxis.size; - const int padding = 10; - final Path backgroundPath = Path(); - final String label = _getYValue(cartesianAxis, position); - - _triggerCrosshairCallback(label, cartesianAxis); - final Size labelSize = measureText(label, textStyle); - Rect labelRect = Rect.fromLTWH( - axisRect.right - - (labelSize.width + padding) - - interactiveTooltip.arrowLength, - position.dy - (labelSize.height + padding) / 2, - labelSize.width + padding, - labelSize.height + padding); - labelRect = _validateRectBounds(labelRect, axisRect); - final Rect validatedRect = - _validateRectYPosition(labelRect, plotAreaBounds); - backgroundPath.reset(); - final RRect tooltipRect = RRect.fromRectAndRadius( - validatedRect, Radius.circular(interactiveTooltip.borderRadius)); - backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - tooltipRect.right, - tooltipRect.top + - tooltipRect.height / 2 - - interactiveTooltip.arrowWidth, - tooltipRect.right, - tooltipRect.bottom - - tooltipRect.height / 2 + - interactiveTooltip.arrowWidth, - tooltipRect.right + interactiveTooltip.arrowLength, - position.dy, - tooltipRect.right + interactiveTooltip.arrowLength, - position.dy); - _drawTooltipText(canvas, label, textStyle, tooltipRect, labelSize); - } - - /// Draw top axis tooltip. - void _drawTopAxisTooltip( - Canvas canvas, - Offset position, - Paint strokePaint, - Paint fillPaint, - Rect plotAreaBounds, - RenderChartAxis? cartesianAxis, - TextStyle textStyle, - Offset plotAreaOffset) { - final Offset parentDataOffset = - (cartesianAxis!.parentData! as BoxParentData).offset; - final Offset axisOffset = - parentDataOffset.translate(-plotAreaOffset.dx, -plotAreaOffset.dy); - final Rect axisRect = axisOffset & cartesianAxis.size; - final InteractiveTooltip interactiveTooltip = - cartesianAxis.interactiveTooltip; - final Path backgroundPath = Path(); - const int padding = 10; - final String label = _getXValue(cartesianAxis, position, plotAreaBounds); - - _triggerCrosshairCallback(label, cartesianAxis); - final Size labelSize = measureText(label, textStyle); - Rect labelRect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + padding / 2), - axisRect.bottom - - (labelSize.height + padding) - - interactiveTooltip.arrowLength, - labelSize.width + padding, - labelSize.height + padding); - labelRect = _validateRectBounds(labelRect, axisRect); - final Rect validatedRect = - _validateRectXPosition(labelRect, plotAreaBounds); - backgroundPath.reset(); - final RRect tooltipRect = RRect.fromRectAndRadius( - validatedRect, Radius.circular(interactiveTooltip.borderRadius)); - backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - position.dx, - tooltipRect.bottom + interactiveTooltip.arrowLength, - (tooltipRect.right - tooltipRect.width / 2) + - interactiveTooltip.arrowWidth, - tooltipRect.bottom, - (tooltipRect.left + tooltipRect.width / 2) - - interactiveTooltip.arrowWidth, - tooltipRect.bottom, - position.dx, - tooltipRect.bottom + interactiveTooltip.arrowLength, - ); - _drawTooltipText(canvas, label, textStyle, tooltipRect, labelSize); - } - - /// Draw right axis tooltip. - void _drawRightAxisTooltip( - Canvas canvas, - Offset position, - Paint strokePaint, - Paint fillPaint, - Rect plotAreaBounds, - RenderChartAxis? cartesianAxis, - TextStyle textStyle, - Offset plotAreaOffset) { - final Offset parentDataOffset = - (cartesianAxis!.parentData! as BoxParentData).offset; - final Offset axisOffset = - parentDataOffset.translate(-plotAreaOffset.dx, -plotAreaOffset.dy); - final Rect axisRect = axisOffset & cartesianAxis.size; - final InteractiveTooltip interactiveTooltip = - cartesianAxis.interactiveTooltip; - const int padding = 10; - final Path backgroundPath = Path(); - final String label = _getYValue(cartesianAxis, position); - - _triggerCrosshairCallback(label, cartesianAxis); - final Size labelSize = measureText(label, textStyle); - Rect labelRect = Rect.fromLTWH( - axisRect.left + interactiveTooltip.arrowLength, - position.dy - (labelSize.height / 2 + padding / 2), - labelSize.width + padding, - labelSize.height + padding); - labelRect = _validateRectBounds(labelRect, axisRect); - final Rect validatedRect = - _validateRectYPosition(labelRect, plotAreaBounds); - backgroundPath.reset(); - final RRect tooltipRect = RRect.fromRectAndRadius( - validatedRect, Radius.circular(interactiveTooltip.borderRadius)); - backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - tooltipRect.left, - tooltipRect.top + - tooltipRect.height / 2 - - interactiveTooltip.arrowWidth, - tooltipRect.left, - tooltipRect.bottom - - tooltipRect.height / 2 + - interactiveTooltip.arrowWidth, - tooltipRect.left - interactiveTooltip.arrowLength, - position.dy, - tooltipRect.left - interactiveTooltip.arrowLength, - position.dy); - _drawTooltipText(canvas, label, textStyle, tooltipRect, labelSize); - } - - void _drawBottomAxisTooltip( - Canvas canvas, - Offset position, - Paint strokePaint, - Paint fillPaint, - Rect plotAreaBounds, - RenderChartAxis? axis, - TextStyle textStyle, - Offset plotAreaOffset) { - final InteractiveTooltip interactiveTooltip = axis!.interactiveTooltip; - final Offset parentDataOffset = (axis.parentData! as BoxParentData).offset; - final Offset axisOffset = - parentDataOffset.translate(-plotAreaOffset.dx, -plotAreaOffset.dy); - final Rect axisBounds = axisOffset & axis.size; - - const int padding = 10; - final Path backgroundPath = Path(); - final String label = _getXValue(axis, position, plotAreaBounds); - - _triggerCrosshairCallback(label, axis); - final Size labelSize = measureText( - label, - textStyle, - ); - Rect labelRect = Rect.fromLTWH( - position.dx - (labelSize.width / 2 + padding / 2), - axisBounds.top + interactiveTooltip.arrowLength, - labelSize.width + padding, - labelSize.height + padding); - labelRect = _validateRectBounds(labelRect, axisBounds); - final Rect validatedRect = - _validateRectXPosition(labelRect, plotAreaBounds); - final RRect tooltipRect = RRect.fromRectAndRadius( - validatedRect, Radius.circular(interactiveTooltip.borderRadius)); - backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( - canvas, - backgroundPath, - fillPaint, - strokePaint, - position.dx, - tooltipRect.top - interactiveTooltip.arrowLength, - (tooltipRect.right - tooltipRect.width / 2) + - interactiveTooltip.arrowWidth, - tooltipRect.top, - (tooltipRect.left + tooltipRect.width / 2) - - interactiveTooltip.arrowWidth, - tooltipRect.top, - position.dx, - tooltipRect.top - interactiveTooltip.arrowLength); - _drawTooltipText(canvas, label, textStyle, tooltipRect, labelSize); - } - - void _drawTooltipText(Canvas canvas, String text, TextStyle textStyle, - RRect tooltipRect, Size labelSize) { - _drawText(canvas, text, _textPosition(tooltipRect, labelSize), - textStyle.copyWith(color: textStyle.color ?? Colors.white), 0); - } - - Offset _textPosition(RRect tooltipRect, Size labelSize) { - return Offset( - (tooltipRect.left + tooltipRect.width / 2) - labelSize.width / 2, - (tooltipRect.top + tooltipRect.height / 2) - labelSize.height / 2); - } - - void _drawText(Canvas canvas, String text, Offset point, TextStyle style, - [int? angle, bool? isRtl]) { - final int maxLines = getMaxLinesContent(text); - final TextSpan span = TextSpan(text: text, style: style); - final TextPainter tp = TextPainter( - text: span, - textDirection: (isRtl ?? false) - ? dart_ui.TextDirection.rtl - : dart_ui.TextDirection.ltr, - textAlign: TextAlign.center, - maxLines: maxLines); - tp.layout(); - canvas.save(); - canvas.translate(point.dx, point.dy); - Offset labelOffset = Offset.zero; - if (angle != null && angle > 0) { - canvas.rotate(degreeToRadian(angle)); - labelOffset = Offset(-tp.width / 2, -tp.height / 2); - } - tp.paint(canvas, labelOffset); - canvas.restore(); - } - - Rect _validateRectBounds(Rect tooltipRect, Rect boundary) { - /// Padding between the corners. - const double padding = 0.5; - Rect validatedRect = tooltipRect; - double difference = 0; - - if (tooltipRect.left < boundary.left) { - difference = (boundary.left - tooltipRect.left) + padding; - validatedRect = validatedRect.translate(difference, 0); - } - if (tooltipRect.right > boundary.right) { - difference = (tooltipRect.right - boundary.right) + padding; - validatedRect = validatedRect.translate(-difference, 0); - } - if (tooltipRect.top < boundary.top) { - difference = (boundary.top - tooltipRect.top) + padding; - validatedRect = validatedRect.translate(0, difference); - } - - if (tooltipRect.bottom > boundary.bottom) { - difference = (tooltipRect.bottom - boundary.bottom) + padding; - validatedRect = validatedRect.translate(0, -difference); - } - return validatedRect; - } - - Rect _validateRectYPosition(Rect labelRect, Rect axisClipRect) { - Rect validatedRect = labelRect; - if (labelRect.bottom >= axisClipRect.bottom) { - validatedRect = Rect.fromLTRB( - labelRect.left, - labelRect.top - (labelRect.bottom - axisClipRect.bottom), - labelRect.right, - axisClipRect.bottom); - } else if (labelRect.top <= axisClipRect.top) { - validatedRect = Rect.fromLTRB( - labelRect.left, - axisClipRect.top, - labelRect.right, - labelRect.bottom + (axisClipRect.top - labelRect.top)); - } - return validatedRect; - } - - /// Gets the x position of validated rect. - Rect _validateRectXPosition(Rect labelRect, Rect axisClipRect) { - Rect validatedRect = labelRect; - if (labelRect.right >= axisClipRect.right) { - validatedRect = Rect.fromLTRB( - labelRect.left - (labelRect.right - axisClipRect.right), - labelRect.top, - axisClipRect.right, - labelRect.bottom); - } else if (labelRect.left <= axisClipRect.left) { - validatedRect = Rect.fromLTRB( - axisClipRect.left, - labelRect.top, - labelRect.right + (axisClipRect.left - labelRect.left), - labelRect.bottom); - } - return validatedRect; - } - - /// Draw tooltip arrow head. - void _drawTooltipArrowhead( - Canvas canvas, - Path backgroundPath, - Paint fillPaint, - Paint strokePaint, - double x1, - double y1, - double x2, - double y2, - double x3, - double y3, - double x4, - double y4) { - backgroundPath.moveTo(x1, y1); - backgroundPath.lineTo(x2, y2); - backgroundPath.lineTo(x3, y3); - backgroundPath.lineTo(x4, y4); - backgroundPath.lineTo(x1, y1); - fillPaint.isAntiAlias = true; - canvas.drawPath(backgroundPath, strokePaint); - canvas.drawPath(backgroundPath, fillPaint); - } - - void _triggerCrosshairCallback(String label, RenderChartAxis axis) { - final RenderBehaviorArea? parent = parentBox as RenderBehaviorArea?; - if (parent == null) { - return; - } - if (parent.onCrosshairPositionChanging != null && - parent.chartThemeData != null) { - final CrosshairRenderArgs crosshairEventArgs = CrosshairRenderArgs( - axis.widget, label, axis.name, AxisOrientation.horizontal); - crosshairEventArgs.text = label; - crosshairEventArgs.lineColor = - lineColor ?? parent.chartThemeData!.crosshairLineColor; - parent.onCrosshairPositionChanging!(crosshairEventArgs); - label = crosshairEventArgs.text; - } - } - - /// To find the x value of crosshair. - String _getXValue( - RenderChartAxis axis, Offset position, Rect plotAreaBounds) { - final num value = axis.pixelToPoint(axis.paintBounds, - position.dx - plotAreaBounds.left, position.dy - plotAreaBounds.top); - String resultantString = - _getInteractiveTooltipLabel(value, axis).toString(); - if (axis.interactiveTooltip.format != null) { - final String stringValue = axis.interactiveTooltip.format! - .replaceAll('{value}', resultantString); - resultantString = stringValue; - } - return resultantString; - } - - /// To find the y value of crosshair. - String _getYValue(RenderChartAxis axis, Offset position) { - final num value = - axis.pixelToPoint(axis.paintBounds, position.dx, position.dy); - String resultantString = - _getInteractiveTooltipLabel(value, axis).toString(); - if (axis.interactiveTooltip.format != null) { - final String stringValue = axis.interactiveTooltip.format! - .replaceAll('{value}', resultantString); - resultantString = stringValue; - } - return resultantString; - } - - /// To get interactive tooltip label. - dynamic _getInteractiveTooltipLabel(num value, RenderChartAxis axis) { - if (axis is RenderCategoryAxis) { - final num index = value < 0 ? 0 : value; - return axis - .visibleLabels[(index.round() >= axis.visibleLabels.length - ? (index.round() > axis.visibleLabels.length - ? axis.visibleLabels.length - 1 - : index - 1) - : index) - .round()] - .text; - } else if (axis is RenderDateTimeCategoryAxis) { - final num index = value < 0 ? 0 : value; - return axis - .visibleLabels[(index.round() >= axis.visibleLabels.length - ? (index.round() > axis.visibleLabels.length - ? axis.visibleLabels.length - 1 - : index - 1) - : index) - .round()] - .text; - } else if (axis is RenderDateTimeAxis) { - final num interval = axis.visibleRange!.minimum.ceil(); - final num previousInterval = (axis.visibleLabels.isNotEmpty) - ? axis.visibleLabels[axis.visibleLabels.length - 1].value - : interval; - final DateFormat dateFormat = axis.dateFormat ?? - _dateTimeLabelFormat( - axis, interval.toInt(), previousInterval.toInt()); - return dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(value.toInt())); - } else { - value = axis is RenderLogarithmicAxis ? pow(10, value) : value; - return _getLabelValue(value, axis, axis.interactiveTooltip.decimalPlaces); - } - } - - /// To get the label format of the date-time axis. - DateFormat _dateTimeLabelFormat(RenderChartAxis axis, - [int? interval, int? prevInterval]) { - DateFormat? format; - final bool notDoubleInterval = - (axis.interval != null && axis.interval! % 1 == 0) || - axis.interval == null; - DateTimeIntervalType? actualIntervalType; - num? minimum; - minimum = axis.visibleRange!.minimum; - actualIntervalType = (axis as RenderDateTimeAxis).visibleIntervalType; - switch (actualIntervalType) { - case DateTimeIntervalType.years: - format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); - break; - case DateTimeIntervalType.months: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - - break; - case DateTimeIntervalType.days: - format = (minimum == interval || interval == prevInterval) - ? _firstLabelFormat(actualIntervalType) - : _dateTimeFormat(actualIntervalType, interval, prevInterval); - break; - case DateTimeIntervalType.hours: - format = DateFormat.j(); - break; - case DateTimeIntervalType.minutes: - format = DateFormat.Hm(); - break; - case DateTimeIntervalType.seconds: - format = DateFormat.ms(); - break; - case DateTimeIntervalType.milliseconds: - final DateFormat dateFormat = DateFormat('ss.SSS'); - format = dateFormat; - break; - case DateTimeIntervalType.auto: - break; - // ignore: no_default_cases - default: - break; - } - return format!; - } - - /// Gets the the actual label value for tooltip and data label etc. - String _getLabelValue(num value, dynamic axis, [int? showDigits]) { - if (value.toString().split('.').length > 1) { - final String str = value.toString(); - final List list = str.split('.'); - value = double.parse(value.toStringAsFixed(showDigits ?? 3)); - value = (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000' || - list[1] == '0000000') - ? value.round() - : value; - } - final dynamic text = axis is RenderNumericAxis && axis.numberFormat != null - ? axis.numberFormat!.format(value) - : value; - return ((axis.labelFormat != null && axis.labelFormat != '') - ? axis.labelFormat.replaceAll(RegExp('{value}'), text.toString()) - : text.toString()) as String; - } - - /// Calculate the dateTime format. - DateFormat? _dateTimeFormat(DateTimeIntervalType? actualIntervalType, - int? interval, int? prevInterval) { - final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); - final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); - DateFormat? format; - final bool isIntervalDecimal = interval % 1 == 0; - if (actualIntervalType == DateTimeIntervalType.months) { - format = minimum.year == maximum.year - ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) - : DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = minimum.month != maximum.month - ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) - : DateFormat.d(); - } - - return format; - } - - /// Returns the first label format for date time values. - DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { - DateFormat? format; - - if (actualIntervalType == DateTimeIntervalType.months) { - format = DateFormat('yyy MMM'); - } else if (actualIntervalType == DateTimeIntervalType.days) { - format = DateFormat.MMMd(); - } else if (actualIntervalType == DateTimeIntervalType.minutes) { - format = DateFormat.Hm(); - } - - return format; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/trackball.dart b/packages/syncfusion_flutter_charts/lib/src/charts/interactions/trackball.dart deleted file mode 100644 index 9607f93e2..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/charts/interactions/trackball.dart +++ /dev/null @@ -1,260 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; - -import '../common/chart_point.dart'; -import '../common/marker.dart'; -import '../utils/enum.dart'; -import 'behavior.dart'; - -/// Options to customize the markers that are displayed when -/// trackball is enabled. -/// -/// Trackball markers are used to provide information about the exact -/// point location, when the trackball is visible. You can add a shape to adorn -/// each data point. Trackball markers can be enabled by using the -/// [markerVisibility] property in [TrackballMarkerSettings]. -/// Provides the options like color, border width, border color and shape of the -/// marker to customize the appearance. -class TrackballMarkerSettings extends MarkerSettings { - /// Creating an argument constructor of TrackballMarkerSettings class. - const TrackballMarkerSettings({ - this.markerVisibility = TrackballVisibilityMode.auto, - super.height, - super.width, - super.color, - super.shape, - super.borderWidth, - super.borderColor, - super.image, - }); - - /// Whether marker should be visible or not when trackball is enabled. - /// - /// The below values are applicable for this: - /// * TrackballVisibilityMode.auto - If the [isVisible] property in the series - /// `markerSettings` is set to true, then the trackball marker will also be - /// displayed for that particular series, else it will not be displayed. - /// * TrackballVisibilityMode.visible - Makes the trackball marker visible - /// for all the series, - /// irrespective of considering the [isVisible] property's value in the - /// `markerSettings`. - /// * TrackballVisibilityMode.hidden - Hides the trackball marker for all - /// the series. - /// - /// Defaults to `TrackballVisibilityMode.auto`. - /// - /// Also refer [TrackballVisibilityMode]. - /// - /// ```dart - /// late TrackballBehavior trackballBehavior; - /// - /// void initState() { - /// trackballBehavior = TrackballBehavior( - /// enable: true, - /// markerSettings: TrackballMarkerSettings( - /// markerVisibility: TrackballVisibilityMode.visible, - /// width: 10 - /// ) - /// ); - /// super.initState(); - /// } - /// - /// Widget build(BuildContext context) { - /// return SfCartesianChart( - /// trackballBehavior: trackballBehavior - /// ); - /// } - ///``` - final TrackballVisibilityMode markerVisibility; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is TrackballMarkerSettings && - other.markerVisibility == markerVisibility && - other.height == height && - other.width == width && - other.color == color && - other.shape == shape && - other.borderWidth == borderWidth && - other.borderColor == borderColor && - other.image == image; - } - - @override - int get hashCode { - final List values = [ - markerVisibility, - height, - width, - color, - shape, - borderWidth, - borderColor, - image - ]; - return Object.hashAll(values); - } -} - -class TrackballInfo { - TrackballInfo({ - required this.position, - this.name, - this.color, - }); - - /// Local position of the tooltip. - final Offset? position; - - /// Specifies the series name. - final String? name; - - /// Specifies the series color. - final Color? color; -} - -/// Class to store trackball tooltip start and end positions. -class TooltipPositions { - /// Creates the parameterized constructor for the class TooltipPositions. - const TooltipPositions(this.tooltipTop, this.tooltipBottom); - - /// Specifies the tooltip top value. - final List tooltipTop; - - /// Specifies the tooltip bottom value. - final List tooltipBottom; -} - -/// Class to store the string values with their corresponding series renderer. -class TrackballElement { - /// Creates the parameterized constructor for the class _TrackballElement. - TrackballElement(this.label, this.seriesRenderer); - - /// Specifies the trackball label value. - final String label; - - /// Specifies the value of cartesian series renderer. - final dynamic seriesRenderer; - - /// Specifies whether to render the trackball element. - bool needRender = true; -} - -class ClosestPoints { - /// Creates the parameterized constructor for class ClosestPoints. - const ClosestPoints( - {required this.closestPointX, required this.closestPointY}); - - /// Holds the closest x point value. - final double closestPointX; - - /// Holds the closest y point value. - final double closestPointY; -} - -/// Represents the chart location. -class ChartLocation { - /// Creates an instance of chart location. - ChartLocation(this.x, this.y); - - /// Specifies the value of x. - double x; - - /// Specifies the value of y. - double y; -} - -class ChartTrackballInfo extends TrackballInfo { - ChartTrackballInfo({ - required super.position, - required this.point, - required this.series, - required this.pointIndex, - required this.seriesIndex, - this.lowYPos, - this.highXPos, - this.highYPos, - this.openXPos, - this.openYPos, - this.closeXPos, - this.closeYPos, - this.minYPos, - this.maxXPos, - this.maxYPos, - this.lowerXPos, - this.lowerYPos, - this.upperXPos, - this.upperYPos, - super.name, - super.color, - }); - - final CartesianChartPoint point; - final dynamic series; - final int pointIndex; - final int seriesIndex; - double? lowYPos; - double? highXPos; - double? highYPos; - double? openXPos; - double? openYPos; - double? closeXPos; - double? closeYPos; - double? minYPos; - double? maxXPos; - double? maxYPos; - double? lowerXPos; - double? lowerYPos; - double? upperXPos; - double? upperYPos; -} - -class TrackballRenderObject extends SingleChildRenderObjectWidget { - const TrackballRenderObject( - {Key? key, - required Widget child, - required this.template, - required this.xPos, - required this.yPos, - required this.trackballBehavior, - this.chartPointInfo, - this.index}) - : super(key: key, child: child); - - final Widget template; - final int? index; - final List? chartPointInfo; - final double xPos; - final double yPos; - final TrackballBehavior trackballBehavior; - - @override - RenderObject createRenderObject(BuildContext context) { - return TrackballTemplateRenderBox( - template, - xPos, - yPos, - trackballBehavior, - chartPointInfo, - index, - ); - } - - @override - void updateRenderObject( - BuildContext context, covariant TrackballTemplateRenderBox renderObject) { - renderObject.template = template; - renderObject.index = index; - renderObject.xPos = xPos; - renderObject.yPos = yPos; - renderObject.trackballBehavior = trackballBehavior; - renderObject.chartPointInfo = chartPointInfo; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart b/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart index 8a8c63eb3..28db24c8a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/pyramid_chart.dart @@ -13,6 +13,7 @@ import 'common/title.dart'; import 'interactions/behavior.dart'; import 'interactions/tooltip.dart'; import 'series/pyramid_series.dart'; +import 'theme.dart'; import 'utils/enum.dart'; import 'utils/helper.dart'; import 'utils/typedef.dart'; @@ -43,18 +44,7 @@ class SfPyramidChart extends StatefulWidget { this.onChartTouchInteractionUp, this.onChartTouchInteractionDown, this.onChartTouchInteractionMove, - this.palette = const [ - Color.fromRGBO(75, 135, 185, 1), - Color.fromRGBO(192, 108, 132, 1), - Color.fromRGBO(246, 114, 128, 1), - Color.fromRGBO(248, 177, 149, 1), - Color.fromRGBO(116, 180, 155, 1), - Color.fromRGBO(0, 168, 181, 1), - Color.fromRGBO(73, 76, 162, 1), - Color.fromRGBO(255, 205, 96, 1), - Color.fromRGBO(255, 240, 219, 1), - Color.fromRGBO(238, 238, 238, 1) - ], + this.palette, this.margin = const EdgeInsets.fromLTRB(10, 10, 10, 10), this.series = const PyramidSeries(), this.title = const ChartTitle(), @@ -178,7 +168,7 @@ class SfPyramidChart extends StatefulWidget { /// ); /// } /// ``` - final List palette; + final List? palette; /// Customizes the tooltip in chart. /// @@ -436,34 +426,50 @@ class SfPyramidChartState extends State late ThemeData _themeData; SfLocalizations? _localizations; - SfChartThemeData _updateThemeData(BuildContext context) { - _chartThemeData = SfChartTheme.of(context); - _themeData = Theme.of(context); - _chartThemeData = _chartThemeData.copyWith( - backgroundColor: - widget.backgroundColor ?? _chartThemeData.backgroundColor, - titleBackgroundColor: - widget.title.backgroundColor ?? _chartThemeData.titleBackgroundColor, + SfChartThemeData _updateThemeData( + BuildContext context, SfChartThemeData effectiveChartThemeData) { + SfChartThemeData chartThemeData = SfChartTheme.of(context); + chartThemeData = chartThemeData.copyWith( + backgroundColor: widget.backgroundColor ?? + chartThemeData.backgroundColor ?? + effectiveChartThemeData.backgroundColor, + titleBackgroundColor: widget.title.backgroundColor ?? + chartThemeData.titleBackgroundColor ?? + effectiveChartThemeData.titleBackgroundColor, legendBackgroundColor: widget.legend.backgroundColor ?? - _chartThemeData.legendBackgroundColor, - titleTextStyle: _themeData.textTheme.bodyMedium! - .copyWith(color: _chartThemeData.titleTextColor, fontSize: 15) - .merge(_chartThemeData.titleTextStyle) + chartThemeData.legendBackgroundColor ?? + effectiveChartThemeData.legendBackgroundColor, + tooltipColor: widget.tooltipBehavior?.color ?? + chartThemeData.tooltipColor ?? + effectiveChartThemeData.tooltipColor, + plotAreaBackgroundColor: chartThemeData.plotAreaBackgroundColor ?? + effectiveChartThemeData.plotAreaBackgroundColor, + titleTextStyle: effectiveChartThemeData.titleTextStyle! + .copyWith( + color: chartThemeData.titleTextColor ?? + effectiveChartThemeData.titleTextColor) + .merge(chartThemeData.titleTextStyle) .merge(widget.title.textStyle), - legendTitleTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTitleColor) - .merge(_chartThemeData.legendTitleTextStyle) + legendTitleTextStyle: effectiveChartThemeData.legendTitleTextStyle! + .copyWith( + color: chartThemeData.legendTitleColor ?? + effectiveChartThemeData.legendTitleColor) + .merge(chartThemeData.legendTitleTextStyle) .merge(widget.legend.title?.textStyle), - legendTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.legendTextColor, fontSize: 13) - .merge(_chartThemeData.legendTextStyle) + legendTextStyle: effectiveChartThemeData.legendTextStyle! + .copyWith( + color: chartThemeData.legendTextColor ?? + effectiveChartThemeData.legendTextColor) + .merge(chartThemeData.legendTextStyle) .merge(widget.legend.textStyle), - tooltipTextStyle: _themeData.textTheme.bodySmall! - .copyWith(color: _chartThemeData.tooltipLabelColor) - .merge(_chartThemeData.tooltipTextStyle) + tooltipTextStyle: effectiveChartThemeData.tooltipTextStyle! + .copyWith( + color: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor) + .merge(chartThemeData.tooltipTextStyle) .merge(widget.tooltipBehavior?.textStyle), ); - return _chartThemeData; + return chartThemeData; } Widget _buildLegendItem(BuildContext context, int index) { @@ -517,8 +523,11 @@ class SfPyramidChartState extends State /// in [SfPyramidChart]. @override Widget build(BuildContext context) { - _chartThemeData = _updateThemeData(context); - final ThemeData themeData = Theme.of(context); + _themeData = Theme.of(context); + final SfChartThemeData effectiveChartThemeData = _themeData.useMaterial3 + ? SfChartThemeDataM3(context) + : SfChartThemeDataM2(context); + _chartThemeData = _updateThemeData(context, effectiveChartThemeData); final core.LegendPosition legendPosition = effectiveLegendPosition(widget.legend); final Axis orientation = @@ -578,20 +587,24 @@ class SfPyramidChartState extends State onLegendTapped: widget.onLegendTapped, onTooltipRender: widget.onTooltipRender, onDataLabelTapped: widget.onDataLabelTapped, - palette: widget.palette, + palette: widget.palette ?? + (_themeData.useMaterial3 + ? (effectiveChartThemeData as SfChartThemeDataM3).palette + : (effectiveChartThemeData as SfChartThemeDataM2).palette), selectionMode: SelectionType.point, selectionGesture: widget.selectionGesture, enableMultiSelection: widget.enableMultiSelection, tooltipBehavior: widget.tooltipBehavior, onSelectionChanged: widget.onSelectionChanged, chartThemeData: _chartThemeData, - themeData: themeData, + themeData: _themeData, children: [widget.series], ), if (widget.tooltipBehavior != null) BehaviorArea( tooltipKey: _tooltipKey, chartThemeData: _chartThemeData, + themeData: _themeData, tooltipBehavior: widget.tooltipBehavior, onTooltipRender: widget.onTooltipRender, children: [ @@ -603,8 +616,8 @@ class SfPyramidChartState extends State opacity: widget.tooltipBehavior!.opacity, borderColor: widget.tooltipBehavior!.borderColor, borderWidth: widget.tooltipBehavior!.borderWidth, - color: widget.tooltipBehavior!.color ?? - _chartThemeData.tooltipColor, + color: (widget.tooltipBehavior!.color ?? + _chartThemeData.tooltipColor)!, showDuration: widget.tooltipBehavior!.duration.toInt(), shadowColor: widget.tooltipBehavior!.shadowColor, elevation: widget.tooltipBehavior!.elevation, diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart index db23d715f..6664dc6b4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/area_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -191,60 +191,6 @@ class AreaSeriesRenderer extends XyDataSeriesRenderer borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is AreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -275,6 +221,7 @@ class AreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _oldHighPoints = []; @@ -285,6 +232,7 @@ class AreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); return; @@ -338,6 +286,7 @@ class AreaSegment extends ChartSegment { } points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); @@ -345,23 +294,8 @@ class AreaSegment extends ChartSegment { _strokePath.reset(); _calculatePoints(xValues, yValues); - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - - switch (series.borderDrawMode) { - case BorderDrawMode.all: - _strokePath = _fillPath; - break; - case BorderDrawMode.top: - _createTopStrokePath(_strokePath, lerpHighPoints); - break; - case BorderDrawMode.excludeBottom: - _createExcludeBottomStrokePath( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - } + // Calculated path here for getting gradient bounds. + _createFillPath(_fillPath, _highPoints, _lowPoints); } void _calculatePoints(List xValues, List yValues) { @@ -377,6 +311,7 @@ class AreaSegment extends ChartSegment { continue; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); _highPoints.add(highPoint); @@ -552,7 +487,13 @@ class AreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final ChartMarker marker = series.markerAt(pointIndex); final num x = chartPoint.xValue!; final num y = chartPoint.y!; @@ -576,7 +517,7 @@ class AreaSegment extends ChartSegment { renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: pointIndex, + pointIndex: actualPointIndex, markerColors: [fillPaint.color], markerType: marker.type, ); @@ -586,64 +527,30 @@ class AreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = points[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { if ((points[i] - position).distance <= pointDistance) { @@ -665,9 +572,38 @@ class AreaSegment extends ChartSegment { @override void calculateSegmentPoints() {} + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + } + } + /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -685,6 +621,7 @@ class AreaSegment extends ChartSegment { _strokePath.reset(); points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _oldHighPoints.clear(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart index 8179efc45..ff68f01ec 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/bar_series.dart @@ -3,11 +3,14 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; import '../common/chart_point.dart'; +import '../common/core_legend.dart'; import '../common/core_tooltip.dart'; +import '../common/legend.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -372,46 +375,33 @@ class BarSeriesRenderer extends XyDataSeriesRenderer } @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; + void handleLegendItemTapped(LegendItem item, bool isToggled) { + if (parent != null && parent!.onLegendTapped != null) { + final ChartLegendItem legendItem = item as ChartLegendItem; + final LegendTapArgs args = LegendTapArgs( + legendItem.series, legendItem.seriesIndex, legendItem.pointIndex); + parent!.onLegendTapped!(args); } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is BarSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; + parent!.behaviorArea?.hideInteractiveTooltip(); + + controller.isVisible = !isToggled; + if (controller.isVisible == !isToggled) { + item.onToggled?.call(); + visibilityBeforeTogglingLegend = isToggled; + animateAllBarSeries(parent!); + } + + if (trendlineContainer != null) { + trendlineContainer!.updateLegendState(item, isToggled); + markNeedsLegendUpdate(); } - return segmentCollection; + markNeedsUpdate(); + } + + @override + void onRealTimeAnimationUpdate() { + super.onRealTimeAnimationUpdate(); + transformValues(); } } @@ -441,12 +431,7 @@ class BarSegment extends ChartSegment with BarSeriesTrackerMixin { return; } - if (series.animationDuration > 0) { - _oldSegmentRect = - RRect.lerp(_oldSegmentRect, segmentRect, segmentAnimationFactor); - } else { - _oldSegmentRect = segmentRect; - } + _oldSegmentRect = segmentRect; } @override @@ -542,21 +527,20 @@ class BarSegment extends ChartSegment with BarSeriesTrackerMixin { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, y), series.pointToPixelY(x, y)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; @@ -577,15 +561,20 @@ class BarSegment extends ChartSegment with BarSeriesTrackerMixin { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - // Draws the tracker bounds. - super.onPaint(canvas); + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } if (segmentRect == null) { return; } - final RRect? paintRRect = - RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + series.parent!.isLegendToggled && _oldSegmentRect != null + ? performLegendToggleAnimation( + series, segmentRect!, _oldSegmentRect!, series.borderRadius) + : RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + if (paintRRect == null || paintRRect.isEmpty) { return; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart index 2b8c02512..aab4d537a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/box_and_whisker_series.dart @@ -3,13 +3,13 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../trendline/trendline.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -296,27 +296,60 @@ class BoxAndWhiskerSeriesRenderer return 2; } + void _resetDataSourceHolders() { + minimumValues.clear(); + maximumValues.clear(); + upperValues.clear(); + lowerValues.clear(); + medianValues.clear(); + meanValues.clear(); + outliersValues.clear(); + } + @override void populateDataSource([ List>? yPaths, List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - minimumValues.clear(); - maximumValues.clear(); - upperValues.clear(); - lowerValues.clear(); + _resetDataSourceHolders(); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); + populateChartPoints(); + } - medianValues.clear(); - meanValues.clear(); + @override + void updateDataPoints( + List? removedIndexes, + List? addedIndexes, + List? replacedIndexes, [ + List>? yPaths, + List>? chaoticYLists, + List>? yLists, + List>? fPaths, + List>? chaoticFLists, + List>? fLists, + ]) { outliersValues.clear(); - - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); - populateChartPoints(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + yPaths, + chaoticYLists, + yLists, + fPaths, + chaoticFLists, + fLists, + ); } + @override + num trackballYValue(int index) => maximumValues[index]; + @override void populateChartPoints({ List? positions, @@ -371,8 +404,7 @@ class BoxAndWhiskerSeriesRenderer super.setData(index, segment); final BoxPlotQuartileValues boxPlotValues = BoxPlotQuartileValues(); - final List boxYValues = chaoticYValues[index]; - _findBoxPlotValues(boxYValues, boxPlotMode, boxPlotValues); + _findBoxPlotValues(yValues[index], boxPlotMode, boxPlotValues); final num minimum = boxPlotValues.minimum ?? double.nan; final num maximum = boxPlotValues.maximum ?? double.nan; @@ -466,6 +498,7 @@ class BoxAndWhiskerSeriesRenderer dataLabelMedianValues, dataLabelOutliersValues, ] + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -640,57 +673,9 @@ class BoxAndWhiskerSeriesRenderer ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.boxAndWhiskerSeries; - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is BoxAndWhiskerSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.maximumValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void dispose() { - maximumValues.clear(); - minimumValues.clear(); - lowerValues.clear(); - upperValues.clear(); - meanValues.clear(); - medianValues.clear(); - outliersValues.clear(); - chaoticYValues.clear(); + _resetDataSourceHolders(); super.dispose(); } } @@ -892,9 +877,10 @@ class BoxAndWhiskerSegment extends ChartSegment { points.add(maxStart); points.add(maxEnd); - final Offset maxConnectorStart = segmentRect!.topCenter; + final Offset maxConnectorStart = + Offset(transformX(x, upperQuartile), transformY(x, upperQuartile)); final Offset maxConnectorEnd = - Offset(maxStart.dx + (maxEnd.dx - maxStart.dx) / 2, maxStart.dy); + Offset(transformX(x, maximum), transformY(x, maximum)); points.add(maxConnectorStart); points.add(maxConnectorEnd); @@ -905,9 +891,10 @@ class BoxAndWhiskerSegment extends ChartSegment { points.add(minStart); points.add(minEnd); - final Offset minConnectorStart = segmentRect!.bottomCenter; + final Offset minConnectorStart = + Offset(transformX(x, lowerQuartile), transformY(x, lowerQuartile)); final Offset minConnectorEnd = - Offset(minStart.dx + (minEnd.dx - minStart.dx) / 2, minStart.dy); + Offset(transformX(x, minimum), transformY(x, minimum)); points.add(minConnectorStart); points.add(minConnectorEnd); @@ -1031,19 +1018,28 @@ class BoxAndWhiskerSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { - final num left = x + series.sbsInfo.minimum; - final num right = x + series.sbsInfo.maximum; + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); + Offset primaryPos; + if (points.isNotEmpty && points.length == 8) { + primaryPos = points[3]; + } else { + primaryPos = Offset(series.pointToPixelX(x, chartPoint.upperQuartile!), + series.pointToPixelY(x, chartPoint.upperQuartile!)); + } + return ChartTrackballInfo( - position: Offset(segmentRect!.center.dx, - series.pointToPixelY((left + right) / 2, maximum)), + position: primaryPos, + maxYPos: series.pointToPixelY(x, chartPoint.maximum!), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, - maxYPos: series.pointToPixelY((left + right) / 2, chartPoint.maximum!), + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart index 73e368d3d..86428024f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/bubble_series.dart @@ -3,12 +3,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../common/element_widget.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -216,31 +216,49 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer } } - final List sizes = []; + final List _chaoticSizes = []; + final List _sizes = []; num _minBubbleSize = double.infinity; num _maxBubbleSize = double.negativeInfinity; + void _resetDataSourceHolders() { + _chaoticSizes.clear(); + _sizes.clear(); + } + @override void populateDataSource([ List?>? yPaths, List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - sizes.clear(); - super.populateDataSource( - >[], - >[], - >[], - >[sizeValueMapper ?? _defaultSize], - >[sizes], - ); + _resetDataSourceHolders(); + if (sortingOrder == SortingOrder.none) { + super.populateDataSource( + >[], + >[], + >[], + >[sizeValueMapper ?? _defaultSize], + >[_sizes], + ); + } else { + super.populateDataSource( + >[], + >[], + >[], + >[sizeValueMapper ?? _defaultSize], + >[_chaoticSizes], + >[_sizes], + ); + } _minBubbleSize = double.infinity; _maxBubbleSize = double.negativeInfinity; - for (final num size in sizes) { + for (final num size in _sizes) { _minBubbleSize = min(_minBubbleSize, size); _maxBubbleSize = max(_maxBubbleSize, size); } @@ -256,18 +274,34 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - super.updateDataPoints( - removedIndexes, - addedIndexes, - replacedIndexes, - >[], - >[], - >[], - >[sizeValueMapper ?? _defaultSize], - >[sizes], - ); + if (sortingOrder == SortingOrder.none) { + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[], + >[], + >[], + >[sizeValueMapper ?? _defaultSize], + >[_sizes], + ); + } else { + _sizes.clear(); + super.updateDataPoints( + removedIndexes, + addedIndexes, + replacedIndexes, + >[], + >[], + >[], + >[sizeValueMapper ?? _defaultSize], + >[_chaoticSizes], + >[_sizes], + ); + } } @override @@ -276,12 +310,12 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer List>? yLists, }) { if (yLists == null) { - yLists = >[sizes]; + yLists = >[_sizes]; positions = [ ChartDataPointType.bubbleSize, ]; } else { - yLists.add(sizes); + yLists.add(_sizes); positions!.add(ChartDataPointType.bubbleSize); } @@ -294,7 +328,7 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer void setData(int index, ChartSegment segment) { super.setData(index, segment); - num radius = sizes[index]; + num radius = _sizes[index]; if (radius.isNaN || sizeValueMapper == null) { radius = minimumRadius; } else { @@ -406,52 +440,9 @@ class BubbleSeriesRenderer extends XyDataSeriesRenderer } } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is BubbleSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = segment.series.xAxis!.pixelToPoint( - rect, position.dx + rect.left, position.dy + rect.top); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double currentX = segment.series.xValues[index].toDouble(); - final double currentY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - currentX) { - if ((touchYValue - currentY).abs() > - (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - currentX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = currentX; - nearPointY = currentY; - delta = touchXValue - currentX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void dispose() { - sizes.clear(); + _resetDataSourceHolders(); super.dispose(); } } @@ -519,7 +510,7 @@ class BubbleSegment extends ChartSegment { x: series.xRawValues[currentSegmentIndex], xValue: x, y: y, - bubbleSize: series.sizes[currentSegmentIndex], + bubbleSize: series._sizes[currentSegmentIndex], ); } @@ -549,15 +540,19 @@ class BubbleSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( position: segmentRect!.center, point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart index 80833da67..62973e50d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/candle_series.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; import 'chart_series.dart'; @@ -156,12 +156,18 @@ class CandleSeriesRenderer extends FinancialSeriesRendererBase late Color color; if (enableSolidCandles) { color = isHollow ? bullColor : bearColor; - final Color? segmentColor = pointColorMapper != null ? null : color; + final Color? segmentColor = pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; updateSegmentColor(segment, segmentColor, borderWidth, fillColor: segmentColor, isLineType: true); } else { color = isBull ? bullColor : bearColor; - final Color? segmentColor = pointColorMapper != null ? null : color; + final Color? segmentColor = pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; updateSegmentColor(segment, segmentColor, borderWidth, fillColor: isHollow ? Colors.transparent : segmentColor, isLineType: true); @@ -174,54 +180,12 @@ class CandleSeriesRenderer extends FinancialSeriesRendererBase final SfChartThemeData chartThemeData = parent!.chartThemeData!; final ThemeData themeData = parent!.themeData!; if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { - return chartThemeData.plotAreaBackgroundColor; + return chartThemeData.plotAreaBackgroundColor!; } else if (chartThemeData.backgroundColor != Colors.transparent) { - return chartThemeData.backgroundColor; + return chartThemeData.backgroundColor!; } return themeData.colorScheme.surface; } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is CandleSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for candle series. @@ -406,22 +370,35 @@ class CandleSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect == null) { - return null; + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { + final CartesianChartPoint chartPoint = _chartPoint(); + Offset preferredPos; + if (points.isNotEmpty) { + preferredPos = Offset( + series.pointToPixelX(x, high), series.pointToPixelY(x, high)); + } else { + preferredPos = + Offset(series.pointToPixelX(x, top), series.pointToPixelX(x, top)); + } + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: series.pointToPixelY(x, high), + lowYPos: series.pointToPixelY(x, bottom), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color == Colors.transparent + ? strokePaint.color + : fillPaint.color, + ); } - - final num left = x + series.sbsInfo.minimum; - return ChartTrackballInfo( - position: segmentRect!.topCenter, - point: _chartPoint(), - series: series, - pointIndex: currentSegmentIndex, - seriesIndex: series.index, - lowYPos: series.pointToPixelY(left, bottom), - highYPos: series.pointToPixelY(left, top), - highXPos: series.pointToPixelX(left, top), - ); + return null; } /// Gets the color of the series. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart index 56cec9d98..11a1c5aa8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/chart_series.dart @@ -16,6 +16,7 @@ import '../axis/datetime_category_axis.dart'; import '../axis/logarithmic_axis.dart'; import '../axis/numeric_axis.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; import '../common/circular_data_label.dart'; @@ -29,7 +30,6 @@ import '../common/empty_points.dart'; import '../common/legend.dart'; import '../common/marker.dart'; import '../interactions/selection.dart'; -import '../interactions/trackball.dart'; import '../trendline/trendline.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; @@ -757,6 +757,7 @@ abstract class ChartSeriesRenderer extends RenderBox bool _isYRangeChanged = true; bool _isResized = true; Image? _markerImage; + bool _canInvokePointerUp = true; RenderChartElementLayoutBuilder? get dataLabelContainer => childForSlot(SeriesSlot.dataLabel) @@ -774,15 +775,22 @@ abstract class ChartSeriesRenderer extends RenderBox @protected bool forceTransformValues = false; - List xRawValues = []; + bool _hasLinearDataSource = true; + bool visibilityBeforeTogglingLegend = false; + final List _chaoticRawXValues = []; - List xValues = []; + List xRawValues = []; final List _chaoticXValues = []; - final List _sortValues = []; + List xValues = []; + final List _chaoticRawSortValues = []; + final List _sortValues = []; + final List _chaoticPointColors = []; + List pointColors = []; final List emptyPointIndexes = []; - List pointColors = []; final List _xNullPointIndexes = []; + List sortedIndexes = []; + List> chartPoints = >[]; List get segments => _segments; @@ -851,8 +859,8 @@ abstract class ChartSeriesRenderer extends RenderBox } } - bool get hasLinearData => _hasLinearData; - bool _hasLinearData = true; + bool get canFindLinearVisibleIndexes => _canFindLinearVisibleIndexes; + bool _canFindLinearVisibleIndexes = true; List? get dataSource => _dataSource; List? _dataSource; @@ -901,6 +909,7 @@ abstract class ChartSeriesRenderer extends RenderBox set sortFieldValueMapper(ChartValueMapper? value) { if (_sortFieldValueMapper != value) { _sortFieldValueMapper = value; + canUpdateOrCreateSegments = true; } } @@ -1045,6 +1054,7 @@ abstract class ChartSeriesRenderer extends RenderBox set sortingOrder(SortingOrder value) { if (_sortingOrder != value) { _sortingOrder = value; + canUpdateOrCreateSegments = true; markNeedsUpdate(); } } @@ -1348,6 +1358,7 @@ abstract class ChartSeriesRenderer extends RenderBox case AnimationStatus.completed: _animationType = AnimationType.none; forceTransformValues = true; + visibilityBeforeTogglingLegend = !_isToggled(); markNeedsLayout(); break; @@ -1397,11 +1408,14 @@ abstract class ChartSeriesRenderer extends RenderBox _chaoticRawXValues.clear(); _chaoticXValues.clear(); _sortValues.clear(); + _chaoticRawSortValues.clear(); + _chaoticPointColors.clear(); xRawValues.clear(); xValues.clear(); emptyPointIndexes.clear(); pointColors.clear(); _xNullPointIndexes.clear(); + sortedIndexes.clear(); } bool _canPopulateDataPoints( @@ -1423,6 +1437,7 @@ abstract class ChartSeriesRenderer extends RenderBox // Here fPath is widget specific feature path. // For example, in pie series's pointRadiusMapper is a feature path. List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { _resetDataSourceHolders(); @@ -1432,10 +1447,11 @@ abstract class ChartSeriesRenderer extends RenderBox if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); final int length = dataSource!.length; final int yPathLength = yPaths!.length; @@ -1471,37 +1487,39 @@ abstract class ChartSeriesRenderer extends RenderBox for (int j = 0; j < fPathLength; j++) { final ChartValueMapper fPath = fPaths[j]; final Object? fValue = fPath(current, i); - fLists![j].add(fValue); + chaoticFLists![j].add(fValue); } } _dataCount = _chaoticXValues.length; _applyEmptyPointModeIfNeeded(chaoticYLists!); - _doSortingIfNeeded(chaoticYLists, yLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); } - void _addPointColorMapper( - List>? fPaths, List>? fLists) { - if (pointColorMapper != null) { - if (fPaths != null) { - fPaths.add(pointColorMapper!); - fLists?.add(pointColors); + void _addPointColorMapper(List>? fPaths, + List>? chaoticFLists, List>? fLists) { + if (fPaths != null && pointColorMapper != null) { + fPaths.add(pointColorMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(pointColors); } else { - fPaths = >[pointColorMapper!]; - fLists = >[pointColors]; + pointColors.clear(); + chaoticFLists?.add(_chaoticPointColors); + fLists?.add(pointColors); } } } - void _addSortValueMapper( - List>? fPaths, List>? fLists) { - if (sortFieldValueMapper != null) { - if (fPaths != null) { - fPaths.add(sortFieldValueMapper!); - fLists?.add(_sortValues); + void _addSortValueMapper(List>? fPaths, + List>? chaoticFLists, List>? fLists) { + if (fPaths != null && sortFieldValueMapper != null) { + fPaths.add(sortFieldValueMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(_sortValues); } else { - fPaths = >[sortFieldValueMapper!]; - fLists = >[_sortValues]; + _sortValues.clear(); + chaoticFLists?.add(_chaoticRawSortValues); + fLists?.add(_sortValues); } } } @@ -1582,27 +1600,30 @@ abstract class ChartSeriesRenderer extends RenderBox } void _doSortingIfNeeded( - List>? chaoticYLists, List>? yLists) { + List>? chaoticYLists, + List>? yLists, + List>? chaoticFLists, + List>? fLists) { if (sortingOrder != SortingOrder.none && chaoticYLists != null && chaoticYLists.isNotEmpty && yLists != null && yLists.isNotEmpty) { - if (_sortValues.isEmpty) { + if (_chaoticRawSortValues.isEmpty) { if (_chaoticRawXValues.isNotEmpty) { - _sortValues.addAll(_chaoticRawXValues); + _chaoticRawSortValues.addAll(_chaoticRawXValues); } else { - _sortValues.addAll(_chaoticXValues); + _chaoticRawSortValues.addAll(_chaoticXValues); } } switch (sortingOrder) { case SortingOrder.ascending: - _sort(chaoticYLists, yLists); + _sort(chaoticYLists, yLists, chaoticFLists, fLists); break; case SortingOrder.descending: - _sort(chaoticYLists, yLists, ascending: false); + _sort(chaoticYLists, yLists, chaoticFLists, fLists, ascending: false); break; case SortingOrder.none: @@ -1610,35 +1631,58 @@ abstract class ChartSeriesRenderer extends RenderBox } } else { xValues.clear(); - xValues = xValues..addAll(_chaoticXValues); + xValues.addAll(_chaoticXValues); xRawValues.clear(); - xRawValues = xRawValues..addAll(_chaoticRawXValues); + xRawValues.addAll(_chaoticRawXValues); } } void _sort(List> chaoticYLists, List> yLists, + List>? chaoticFLists, List>? fLists, {bool ascending = true}) { - final int yLength = yLists.length; - final List orderedIndexes = _sortedIndexes(ascending); - final void Function(int index) copyX = - _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; - if (orderedIndexes.isNotEmpty) { - for (final int sortedIndex in orderedIndexes) { - copyX(sortedIndex); - for (int i = 0; i < yLength; i++) { - final List yValues = yLists[i]; - final List chaoticYValues = chaoticYLists[i]; + _computeSortedIndexes(ascending); + if (sortedIndexes.isNotEmpty) { + final void Function(int index, num xValue) copyX = + _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; + final int yLength = yLists.length; + final int fLength = fLists!.length; + final int length = sortedIndexes.length; + + xValues.clear(); + xRawValues.clear(); + + for (int i = 0; i < length; i++) { + final int sortedIndex = sortedIndexes[i]; + final num xValue = _chaoticXValues[sortedIndex]; + copyX(sortedIndex, xValue); + for (int j = 0; j < yLength; j++) { + final List yValues = yLists[j]; + final List chaoticYValues = chaoticYLists[j]; yValues.add(chaoticYValues[sortedIndex]); } + + for (int k = 0; k < fLength; k++) { + final List fValues = fLists[k]; + final List chaoticFValues = chaoticFLists![k]; + fValues.add(chaoticFValues[sortedIndex]); + } + + // During sorting, determine data is linear or non-linear for + // calculating visibleIndexes for proper axis range & segment rendering. + if (_canFindLinearVisibleIndexes) { + _canFindLinearVisibleIndexes = isValueLinear(i, xValue, xValues); + } } } } - List _sortedIndexes(bool ascending) { - int length = _sortValues.length; - final List sortedIndexes = - List.generate(length, (int index) => index); - final dynamic start = _sortValues[0]; + void _computeSortedIndexes(bool ascending) { + sortedIndexes.clear(); + int length = _chaoticRawSortValues.length; + for (int i = 0; i < length; i++) { + sortedIndexes.add(i); + } + final dynamic start = _chaoticRawSortValues[0]; late dynamic canSwap; if (start is num) { canSwap = ascending ? _compareNumIsAscending : _compareNumIsDescending; @@ -1654,8 +1698,8 @@ abstract class ChartSeriesRenderer extends RenderBox for (int i = 0; i < length - 1; i++) { final int currentIndex = sortedIndexes[i]; final int nextIndex = sortedIndexes[i + 1]; - // ignore: avoid_dynamic_calls - if (canSwap(_sortValues[nextIndex], _sortValues[currentIndex])) { + if (canSwap(_chaoticRawSortValues[nextIndex], + _chaoticRawSortValues[currentIndex])) { sortedIndexes[i] = nextIndex; sortedIndexes[i + 1] = currentIndex; swapped = true; @@ -1663,8 +1707,6 @@ abstract class ChartSeriesRenderer extends RenderBox } length--; } while (swapped); - - return sortedIndexes; } bool _compareNumIsAscending(num a, num b) => a < b; @@ -1679,13 +1721,13 @@ abstract class ChartSeriesRenderer extends RenderBox bool _compareStringDescending(String a, String b) => a.compareTo(b) > 0; - void _copyXAndRawXValue(int index) { - xValues.add(_chaoticXValues[index]); + void _copyXAndRawXValue(int index, num xValue) { + _copyXValue(index, xValue); xRawValues.add(_chaoticRawXValues[index]); } - void _copyXValue(int index) { - xValues.add(_chaoticXValues[index]); + void _copyXValue(int index, num xValue) { + xValues.add(xValue); } void populateChartPoints({ @@ -1858,7 +1900,31 @@ abstract class ChartSeriesRenderer extends RenderBox void handlePointerDown(PointerDownEvent details) {} - void handlePointerUp(PointerUpEvent details) {} + void handleScaleUpdate(ScaleUpdateDetails details) { + if (details.scale != 0) { + _canInvokePointerUp = false; + } + } + + void handlePointerUp(PointerUpEvent details) { + final Offset localPosition = globalToLocal(details.position); + if (onPointTap != null && + _interactiveSegment != null && + _canInvokePointerUp) { + final int pointIndex = + dataPointIndex(localPosition, _interactiveSegment!); + final int segPointIndex = + segmentPointIndex(localPosition, _interactiveSegment!); + final ChartPointDetails pointDetails = ChartPointDetails( + index, + viewportIndex(segPointIndex), + chartPoints, + pointIndex, + ); + onPointTap!(pointDetails); + } + _canInvokePointerUp = true; + } void handlePointerHover(PointerHoverEvent details) { final Offset localPosition = globalToLocal(details.position); @@ -1875,6 +1941,7 @@ abstract class ChartSeriesRenderer extends RenderBox } void handleLongPressStart(LongPressStartDetails details) { + _canInvokePointerUp = false; final Offset localPosition = globalToLocal(details.globalPosition); if (onPointLongPress != null && _interactiveSegment != null) { final int pointIndex = @@ -1901,20 +1968,6 @@ abstract class ChartSeriesRenderer extends RenderBox void handleTapUp(TapUpDetails details) { final Offset localPosition = globalToLocal(details.globalPosition); - if (onPointTap != null && _interactiveSegment != null) { - final int pointIndex = - dataPointIndex(localPosition, _interactiveSegment!); - final int segPointIndex = - segmentPointIndex(localPosition, _interactiveSegment!); - final ChartPointDetails pointDetails = ChartPointDetails( - index, - viewportIndex(segPointIndex), - chartPoints, - pointIndex, - ); - onPointTap!(pointDetails); - } - if (parent != null && _interactiveSegment != null) { final bool hasSelection = _selectionEnabled && parent!.selectionGesture == ActivationMode.singleTap; @@ -1979,7 +2032,7 @@ abstract class ChartSeriesRenderer extends RenderBox int viewportIndex(int index, [List? visibleIndexes]) { if (visibleIndexes != null && visibleIndexes.isNotEmpty) { - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { final int start = visibleIndexes[0]; final int end = visibleIndexes[1] + 1; int viewportIndex = 0; @@ -2110,7 +2163,6 @@ abstract class ChartSeriesRenderer extends RenderBox } } - @protected ChartSegment segmentAt(int segmentPointIndex) { return segments[segmentPointIndex]; } @@ -2464,7 +2516,8 @@ abstract class ChartSegment { /// Specifies the segment has empty point. bool isEmpty = false; - /// Specifies the segment is visible or not. + /// Specifies the segment is visible or not for circular, funnel and pyramid segments only. + /// Not applicable for cartesian segments. bool isVisible = true; void copyOldSegmentValues( @@ -2472,7 +2525,7 @@ abstract class ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) => null; - TrackballInfo? trackballInfo(Offset position) => null; + TrackballInfo? trackballInfo(Offset position, int pointIndex) => null; /// To dispose the objects. void dispose() { @@ -2683,9 +2736,10 @@ class ChartSeriesController { final BoxParentData parentData = seriesRenderer.parent!.parentData! as BoxParentData; - final Rect seriesBounds = parentData.offset & seriesRenderer.parent!.size; - double xValue = seriesRenderer.xAxis!.pixelToPoint(seriesBounds, - position.dx - seriesBounds.left, position.dy - seriesBounds.top); + final Rect seriesBounds = seriesRenderer.paintBounds; + position -= parentData.offset; + double xValue = seriesRenderer.xAxis! + .pixelToPoint(seriesBounds, position.dx, position.dy); final num yValue = seriesRenderer.yAxis! .pixelToPoint(seriesBounds, position.dx, position.dy); @@ -2699,6 +2753,18 @@ class ChartSeriesController { D? _rawXValue(CartesianSeriesRenderer seriesRenderer, num xValue) { final int index = seriesRenderer.xValues.indexOf(xValue); + final RenderChartAxis xAxis = seriesRenderer.xAxis!; + + if (index == -1) { + if (xAxis is RenderDateTimeAxis) { + return DateTime.fromMillisecondsSinceEpoch(xValue.toInt()) as D; + } else if (xAxis is RenderCategoryAxis || + xAxis is RenderDateTimeCategoryAxis) { + return xValue.toString() as D; + } else { + return xValue as D; + } + } return index != -1 ? seriesRenderer.xRawValues[index] : null; } @@ -3198,18 +3264,22 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer ]; } + @override + RenderCartesianChartPlotArea? get parent => + super.parent as RenderCartesianChartPlotArea?; + @override set dataSource(List? value) { if (value == null || value.isEmpty) { _dataCount = 0; segments.clear(); - includeRange = false; markNeedsUpdate(); } if (_dataCount != value?.length || !listEquals(_dataSource, value)) { _dataSource = value; canUpdateOrCreateSegments = true; + parent?.isLegendToggled = false; if (xAxis != null && yAxis != null && parent != null && @@ -3407,6 +3477,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer // Here fPath is widget specific feature path. // For example, in bubble series's bubbleSizeMapper is a feature path. List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { _resetDataSourceHolders(); @@ -3416,10 +3487,11 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); final int length = dataSource!.length; final int yPathLength = yPaths!.length; @@ -3445,8 +3517,8 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer addXValue(rawX, currentX); xMinimum = min(xMinimum, currentX); xMaximum = max(xMaximum, currentX); - if (_hasLinearData) { - _hasLinearData = currentX >= previousX; + if (_hasLinearDataSource) { + _hasLinearDataSource = currentX >= previousX; } for (int j = 0; j < yPathLength; j++) { @@ -3467,7 +3539,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer for (int j = 0; j < fPathLength; j++) { final ChartValueMapper fPath = fPaths[j]; final Object? fValue = fPath(current, i); - fLists![j].add(fValue); + chaoticFLists![j].add(fValue); } previousX = currentX; @@ -3478,15 +3550,13 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer yMin = yMinimum; yMax = yMaximum; _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; _applyEmptyPointModeIfNeeded(chaoticYLists!); - _doSortingIfNeeded(chaoticYLists, yLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); computeNonEmptyYValues(); _populateTrendlineDataSource(); - - if (xAxis is RenderCategoryAxis) { - (xAxis! as RenderCategoryAxis).updateXValuesWithArrangeByIndex(); - } + _updateXValuesForCategoryTypeAxes(); } Function(int, D) _preferredXValue() { @@ -3501,12 +3571,22 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer return _valueAsNum; } + void _updateXValuesForCategoryTypeAxes() { + if (xAxis is RenderCategoryAxis) { + (xAxis! as RenderCategoryAxis).updateXValuesWithArrangeByIndex(); + } else if (xAxis is RenderDateTimeCategoryAxis) { + (xAxis! as RenderDateTimeCategoryAxis).updateXValues(); + } + } + @protected void _populateTrendlineDataSource() {} @protected void computeNonEmptyYValues() {} + num trackballYValue(int index) => index; + /// Method excepts [BoxAndWhiskerSeries], and stacking series. @override void populateChartPoints({ @@ -3546,8 +3626,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer @override DoubleRange range(RenderChartAxis axis) { - final RenderCartesianChartPlotArea? plotArea = - parent as RenderCartesianChartPlotArea?; + final RenderCartesianChartPlotArea? plotArea = parent; if (axis == yAxis && axis.anchorRangeToVisiblePoints && plotArea != null && @@ -3616,7 +3695,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer range = baseRange; } - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { final int end = dataCount - 1; final int startIndex = findIndex(range.minimum, xValues, end: end); final int endIndex = findIndex(range.maximum, xValues, end: end); @@ -3661,8 +3740,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer } } - final RenderCartesianChartPlotArea? plotArea = - parent as RenderCartesianChartPlotArea?; + final RenderCartesianChartPlotArea? plotArea = parent; if (yAxis != null && yAxis!.anchorRangeToVisiblePoints && plotArea != null && @@ -3683,7 +3761,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer DoubleRange _calculateYRange({List>? yLists}) { num minimum = double.infinity; num maximum = double.negativeInfinity; - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { if (visibleIndexes.isNotEmpty) { final int start = visibleIndexes[0]; final int end = visibleIndexes[1]; @@ -3748,9 +3826,9 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer final SfChartThemeData chartThemeData = parent!.chartThemeData!; final ThemeData themeData = parent!.themeData!; if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { - return chartThemeData.plotAreaBackgroundColor; + return chartThemeData.plotAreaBackgroundColor!; } else if (chartThemeData.backgroundColor != Colors.transparent) { - return chartThemeData.backgroundColor; + return chartThemeData.backgroundColor!; } return themeData.colorScheme.surface; } @@ -3816,9 +3894,11 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer case ChartDataLabelAlignment.auto: case ChartDataLabelAlignment.middle: if (isTransposed) { + translationX = -margin.left - size.width / 2; translationY = -margin.top; } else { translationX = -margin.left; + translationY = -margin.top - size.height / 2; } return translateTransform( current.x!, current.y!, translationX, translationY); @@ -3832,7 +3912,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer } final int segmentsCount = segments.length; - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { if (visibleIndexes.isNotEmpty) { final int start = visibleIndexes[0]; final int end = visibleIndexes[1]; @@ -3895,9 +3975,6 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer @override void performLayout() { super.performLayout(); - if (xAxis is RenderCategoryAxis) { - (xAxis! as RenderCategoryAxis).updateXValuesWithArrangeByIndex(); - } trendlineContainer?.layout(constraints); } @@ -3912,7 +3989,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer } final int segmentsCount = segments.length; - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { if (visibleIndexes.isNotEmpty) { final int start = visibleIndexes[0]; final int end = visibleIndexes[1]; @@ -3944,6 +4021,9 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer LinearGradient? gradient, LinearGradient? borderGradient, }) { + segment.fillPaint.shader = null; + segment.strokePaint.shader = null; + if (!segment.isEmpty) { if (onCreateShader != null) { final ShaderDetails details = ShaderDetails(paintBounds, 'series'); @@ -3982,7 +4062,7 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer if (segments.isNotEmpty) { context.canvas.save(); context.canvas.clipRect(paintBounds); - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { if (visibleIndexes.isNotEmpty) { final int start = visibleIndexes[0]; final int end = visibleIndexes[1]; @@ -4183,7 +4263,11 @@ mixin ContinuousSeriesMixin on CartesianSeriesRenderer { @override void transformValues() { - if (xAxis == null || yAxis == null || segments.isEmpty) { + if (xAxis == null || + yAxis == null || + segments.isEmpty || + xAxis!.visibleRange == null || + yAxis!.visibleRange == null) { return; } @@ -4289,6 +4373,7 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (!_canPopulateDataPoints(yPaths, chaoticYLists)) { @@ -4297,24 +4382,25 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); if (removedIndexes != null) { - _removeDataPoints( - removedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _removeDataPoints(removedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (addedIndexes != null) { - _addDataPoints( - addedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _addDataPoints(addedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (replacedIndexes != null) { - _replaceDataPoints( - replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } createOrUpdateSegments(); @@ -4328,26 +4414,22 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final int chaoticYLength = chaoticYLists?.length ?? 0; - final int yListsLength = yLists?.length ?? 0; - // final int yPathLength = yPaths?.length ?? 0; final int fPathLength = fPaths?.length ?? 0; for (final int index in indexes) { _removeXValueAt(index); + _removeRawSortValueAt(index); for (int i = 0; i < chaoticYLength; i++) { if (index < chaoticYLists![i].length) { chaoticYLists[i].removeAt(index); } } - for (int j = 0; j < yListsLength; j++) { - yLists![j].removeAt(index); - } - for (int k = 0; k < fPathLength; k++) { - fLists![k].removeAt(index); + chaoticFLists![k].removeAt(index); } if (emptyPointIndexes.contains(index)) { @@ -4358,8 +4440,8 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { _dataCount = _chaoticXValues.length; // Collecting previous and next index to update them. final List mutableIndexes = _findMutableIndexes(indexes); - _replaceDataPoints( - mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } void _addDataPoints( @@ -4368,6 +4450,7 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final int yPathLength = yPaths!.length; @@ -4398,18 +4481,25 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { index > xRawValues.length - 1 ? _chaoticRawXValues.add(rawX) : _chaoticRawXValues.insert(index, rawX); + + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none) { + index > _chaoticRawSortValues.length - 1 + ? _chaoticRawSortValues.add(rawX) + : _chaoticRawSortValues.insert(index, rawX); + } } } for (int j = 0; j < fPathLength; j++) { final Object? fValue = fPaths![j](current, j); - fLists![j].insert(index, fValue); + chaoticFLists![j].insert(index, fValue); } } _dataCount = _chaoticXValues.length; _applyEmptyPointModeIfNeeded(chaoticYLists!); - _doSortingIfNeeded(chaoticYLists, yLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); } void _replaceDataPoints( @@ -4418,6 +4508,7 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final int yPathLength = yPaths?.length ?? 0; @@ -4448,6 +4539,11 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { chaoticYLists[i][index] = yValue; _chaoticXValues[index] = xValues[index]; _chaoticRawXValues[index] = rawX; + + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none) { + _chaoticRawSortValues[index] = rawX; + } } if (emptyPointIndexes.contains(index)) { emptyPointIndexes.remove(index); @@ -4456,13 +4552,13 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { } for (int j = 0; j < fPathLength; j++) { - fLists![j][index] = fPaths![j](current, j); + chaoticFLists![j][index] = fPaths![j](current, j); } } } _applyEmptyPointModeIfNeeded(chaoticYLists!); - _doSortingIfNeeded(chaoticYLists, yLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); } void _removeXValueAt(int index) { @@ -4474,6 +4570,14 @@ mixin RealTimeUpdateMixin on ChartSeriesRenderer { } } + void _removeRawSortValueAt(int index) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.removeAt(index); + } + } + List _findMutableIndexes(List indexes) { final List mutableIndexes = []; for (final int index in indexes) { @@ -4502,6 +4606,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { // Here fPath is widget specific feature path. // For example, in bubble series's bubbleSizeMapper is a feature path. List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (xValueMapper == null || @@ -4514,34 +4619,36 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); if (removedIndexes != null) { - _removeDataPoints( - removedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _removeDataPoints(removedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (addedIndexes != null) { - _addDataPoints( - addedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _addDataPoints(addedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (replacedIndexes != null) { - _replaceDataPoints( - replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } _applyEmptyPointModeIfNeeded(chaoticYLists); - _doSortingIfNeeded(chaoticYLists, yLists); + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); final DoubleRange xRange = _findMinMaxXRange(xValues); final DoubleRange yRange = _findMinMaxYRange(chaoticYLists); _updateAxisRange( xRange.minimum, xRange.maximum, yRange.minimum, yRange.maximum); computeNonEmptyYValues(); _populateTrendlineDataSource(); + _updateXValuesForCategoryTypeAxes(); canUpdateOrCreateSegments = true; markNeedsLayout(); @@ -4553,6 +4660,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { // Removing a data point can cause the following: @@ -4569,10 +4677,6 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { // - The auto position of data labels will be affected for // continuous series. final int chaoticYLength = chaoticYLists?.length ?? 0; - // TODO(Natrayansf): Real time update. - // Used the yListLength instead yPathLength. - final int yListsLength = yLists?.length ?? 0; - // final int yPathLength = yPaths?.length ?? 0; final int fPathLength = fPaths?.length ?? 0; for (final int index in indexes) { if (index < 0 || index >= _dataCount) { @@ -4580,16 +4684,13 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { } _removeXValueAt(index); + _removeRawSortValueAt(index); for (int i = 0; i < chaoticYLength; i++) { chaoticYLists![i].removeAt(index); } - for (int j = 0; j < yListsLength; j++) { - yLists![j].removeAt(index); - } - for (int k = 0; k < fPathLength; k++) { - fLists![k].removeAt(index); + chaoticFLists![k].removeAt(index); } if (emptyPointIndexes.contains(index)) { @@ -4600,8 +4701,8 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { _dataCount = _chaoticXValues.length; // Collecting previous and next index to update them. final List mutableIndexes = _findMutableIndexes(indexes); - _replaceDataPoints( - mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } void _addDataPoints( @@ -4610,6 +4711,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { // Updating a data point can cause the following: @@ -4630,6 +4732,8 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { final Function(int, D) preferredXValue = _preferredXValue(); final Function(int, D?, num) insertXValue = _insertXValueIntoRawAndChaoticXLists; + final Function(int, D?) insertRawSortValue = + _insertRawXValueIntoChaoticRawSortValue; num xMinimum = double.infinity; num xMaximum = double.negativeInfinity; @@ -4649,10 +4753,11 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { final num currentX = preferredXValue(index, rawX); insertXValue(index, rawX, currentX); + insertRawSortValue(index, rawX); xMinimum = min(xMinimum, currentX); xMaximum = max(xMaximum, currentX); - if (_hasLinearData) { - _hasLinearData = _isValueLinear(index, currentX, _chaoticXValues); + if (_hasLinearDataSource) { + _hasLinearDataSource = isValueLinear(index, currentX, _chaoticXValues); } for (int i = 0; i < yPathLength; i++) { @@ -4671,11 +4776,12 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { for (int j = 0; j < fPathLength; j++) { final Object? fValue = fPaths![j](current, j); - fLists![j].insert(index, fValue); + chaoticFLists![j].insert(index, fValue); } } _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; } void _updateAxisRange( @@ -4719,6 +4825,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { // Updating a data point can cause the following: @@ -4737,6 +4844,9 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { final Function(int, D) preferredXValue = _preferredXValue(); final Function(int, D?, num) replaceXValue = _updateXValueIntoRawAndChaoticXLists; + final Function(int, D?) replaceRawSortValue = + _updateRawXValueIntoChaoticRawSortValue; + final int yPathLength = yPaths?.length ?? 0; final int fPathLength = fPaths?.length ?? 0; @@ -4757,6 +4867,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { final num currentX = preferredXValue(index, rawX); replaceXValue(index, rawX, currentX); + replaceRawSortValue(index, rawX); for (int i = 0; i < yPathLength; i++) { final num? yValue = yPaths![i]!(current, i); if (yValue == null || yValue.isNaN) { @@ -4773,7 +4884,7 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { } for (int j = 0; j < fPathLength; j++) { - fLists![j][index] = fPaths![j](current, j); + chaoticFLists![j][index] = fPaths![j](current, j); } } } @@ -4833,21 +4944,28 @@ mixin CartesianRealTimeUpdateMixin on CartesianSeriesRenderer { _chaoticXValues.insert(index, preferred); } - bool _isValueLinear(int index, num value, List values) { - final int length = values.length; - if (length == 0) { - return true; + void _removeRawSortValueAt(int index) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.removeAt(index); } + } - if (index == 0) { - return length == 1 || value <= values[index + 1]; + void _updateRawXValueIntoChaoticRawSortValue(int index, D? raw) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues[index] = raw; } + } - if (index == length - 1) { - return value >= values[index - 1]; + void _insertRawXValueIntoChaoticRawSortValue(int index, D? raw) { + if (sortFieldValueMapper == null && + sortingOrder != SortingOrder.none && + _chaoticRawSortValues.isNotEmpty) { + _chaoticRawSortValues.insert(index, raw); } - - return value >= values[index - 1] && value <= values[index + 1]; } List _findMutableIndexes(List indexes) { @@ -4913,15 +5031,17 @@ mixin SbsSeriesMixin on CartesianSeriesRenderer { List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); if (dataCount < 1) { return; } - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { _sortedXValues = xValues; } else { final List xValuesCopy = [...xValues]; @@ -4930,16 +5050,34 @@ mixin SbsSeriesMixin on CartesianSeriesRenderer { } num minDelta = double.infinity; - final int length = _sortedXValues.length - 1; - for (int i = 0; i < length; i++) { - final num? current = _sortedXValues[i]; - final num? next = _sortedXValues[i + 1]; - if (current == null || next == null) { - continue; + final int length = _sortedXValues.length; + if (length == 1) { + DateTime? minDate; + num? minimumInSeconds; + if (xAxis is RenderDateTimeAxis) { + minDate = DateTime.fromMillisecondsSinceEpoch(_sortedXValues[0] as int); + minDate = minDate.subtract(const Duration(days: 1)); + minimumInSeconds = minDate.millisecondsSinceEpoch; + } + final num seriesMin = + (xAxis is RenderDateTimeAxis && xRange.minimum == xRange.maximum) + ? minimumInSeconds! + : xRange.minimum; + final num minVal = xValues[0] - seriesMin; + if (minVal != 0) { + minDelta = min(minDelta, minVal); } + } else { + for (int i = 0; i < length - 1; i++) { + final num? current = _sortedXValues[i]; + final num? next = _sortedXValues[i + 1]; + if (current == null || next == null) { + continue; + } - final num delta = (next - current).abs(); - minDelta = min(delta == 0 ? minDelta : delta, minDelta); + final num delta = (next - current).abs(); + minDelta = min(delta == 0 ? minDelta : delta, minDelta); + } } primaryAxisAdjacentDataPointsMinDiff = minDelta.isInfinite ? 1 : minDelta; @@ -5290,6 +5428,7 @@ mixin BarSeriesTrackerMixin on ChartSegment { @override void dispose() { _trackerRect = null; + super.dispose(); } } @@ -5311,7 +5450,7 @@ mixin LineSeriesMixin on CartesianSeriesRenderer { return; } - if (_hasLinearData) { + if (canFindLinearVisibleIndexes) { if (visibleIndexes.isNotEmpty) { final int start = visibleIndexes[0]; int end = visibleIndexes[1]; @@ -5442,11 +5581,15 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer ChartValueMapper? yValueMapper; + void _resetYLists() { + yValues.clear(); + nonEmptyYValues.clear(); + } + @override void _resetDataSourceHolders() { _chaoticYValues.clear(); - yValues.clear(); - nonEmptyYValues.clear(); + _resetYLists(); super._resetDataSourceHolders(); } @@ -5456,6 +5599,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -5474,7 +5618,8 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer } } - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); if (this is! WaterfallSeriesRenderer) { populateChartPoints(); } @@ -5489,6 +5634,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -5502,12 +5648,13 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer if (sortingOrder == SortingOrder.none) { chaoticYLists?.add(yValues); } else { + _resetYLists(); chaoticYLists?.add(_chaoticYValues); yLists?.add(yValues); } } super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); } @override @@ -5522,6 +5669,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer @override void computeNonEmptyYValues() { + nonEmptyYValues.clear(); if (emptyPointSettings.mode == EmptyPointMode.gap || emptyPointSettings.mode == EmptyPointMode.drop) { final List yValuesCopy = [...yValues]; @@ -5534,7 +5682,8 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer } } } else { - nonEmptyYValues = yValues; + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; } } @@ -5544,6 +5693,9 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer seriesYValues: nonEmptyYValues); } + @override + num trackballYValue(int index) => yValues[index]; + @override void populateChartPoints({ List? positions, @@ -5586,6 +5738,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer ..xRawValues = xRawValues ..xValues = xValues ..yLists = >[yValues] + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -5595,8 +5748,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer @override void dispose() { _chaoticYValues.clear(); - yValues.clear(); - nonEmptyYValues.clear(); + _resetYLists(); super.dispose(); } } @@ -5818,22 +5970,18 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer // It both specifies for stacking 100 series. bool _isStacked100 = false; - List _percentageValues = []; + Map _percentageValues = {}; // Stores StackYValues considering empty point modes with yValues. List _stackYValues = []; @override - RenderCartesianChartPlotArea? get parent => - super.parent as RenderCartesianChartPlotArea?; - - @override - void _resetDataSourceHolders() { + void _resetYLists() { topValues.clear(); bottomValues.clear(); _percentageValues.clear(); _stackYValues.clear(); - super._resetDataSourceHolders(); + super._resetYLists(); } @nonVirtual @@ -5855,7 +6003,8 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer } } } else { - _stackYValues = yValues; + final List yValuesCopy = [...yValues]; + _stackYValues = yValuesCopy; } } @@ -5876,20 +6025,17 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer List<_StackingInfo>? positiveValues; List<_StackingInfo>? negativeValues; - if (series.runtimeType.toString().contains('100')) { + if (series is Stacking100SeriesMixin) { _isStacked100 = true; _calculatePercentageValues(yDependents); } for (final AxisDependent yDependant in yDependents) { - StackedSeriesRenderer? yDependantSeries; - if (yDependant is StackedSeriesRenderer) { - yDependantSeries = yDependant; + if (yDependant is! StackedSeriesRenderer) { + continue; } - if (yDependantSeries != null) { - current = yDependantSeries; - } + current = yDependant; if (current == null) { continue; @@ -5906,10 +6052,10 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer if (positiveValues == null || negativeValues == null) { positiveValues = <_StackingInfo>[]; - currentPositiveStackInfo = _StackingInfo(groupName, []); + currentPositiveStackInfo = _StackingInfo(groupName, {}); positiveValues.add(currentPositiveStackInfo); negativeValues = <_StackingInfo>[]; - negativeValues.add(_StackingInfo(groupName, [])); + negativeValues.add(_StackingInfo(groupName, {})); } _computeStackedValues(current, currentPositiveStackInfo, positiveValues, @@ -5936,75 +6082,72 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer final String seriesType = current.runtimeType.toString().toLowerCase(); final bool isStackedArea = seriesType.contains('stackedarea'); final bool isStackedLine = seriesType.contains('stackedline'); - final bool isDropOrGapMode = - current.emptyPointSettings.mode == EmptyPointMode.drop || - current.emptyPointSettings.mode == EmptyPointMode.gap; - + final EmptyPointMode emptyPointMode = current.emptyPointSettings.mode; + final bool isDropOrGapMode = emptyPointMode == EmptyPointMode.drop || + emptyPointMode == EmptyPointMode.gap; final List actualYValues = [...current._stackYValues]; _StackingInfo? currentNegativeStackInfo; - num stackValue; - num yValue; - num yMinimum = double.infinity; num yMaximum = double.negativeInfinity; - current.topValues.clear(); current.bottomValues.clear(); - final int length = current.dataCount; - for (int i = 0; i < length; i++) { - yValue = actualYValues[i]; - if (positiveValues.isNotEmpty) { - for (int k = 0; k < positiveValues.length; k++) { - if (groupName == positiveValues[k].groupName) { - currentPositiveStackInfo = positiveValues[k]; - break; - } else if (k == positiveValues.length - 1) { - currentPositiveStackInfo = _StackingInfo(groupName, []); - positiveValues.add(currentPositiveStackInfo); - } + if (positiveValues.isNotEmpty) { + for (int k = 0; k < positiveValues.length; k++) { + if (groupName == positiveValues[k].groupName) { + currentPositiveStackInfo = positiveValues[k]; + break; + } else if (k == positiveValues.length - 1) { + currentPositiveStackInfo = _StackingInfo(groupName, {}); + positiveValues.add(currentPositiveStackInfo); } } + } - if (negativeValues.isNotEmpty) { - for (int k = 0; k < negativeValues.length; k++) { - if (groupName == negativeValues[k].groupName) { - currentNegativeStackInfo = negativeValues[k]; - break; - } else if (k == negativeValues.length - 1) { - currentNegativeStackInfo = _StackingInfo(groupName, []); - negativeValues.add(currentNegativeStackInfo); - } + if (negativeValues.isNotEmpty) { + for (int k = 0; k < negativeValues.length; k++) { + if (groupName == negativeValues[k].groupName) { + currentNegativeStackInfo = negativeValues[k]; + break; + } else if (k == negativeValues.length - 1) { + currentNegativeStackInfo = _StackingInfo(groupName, {}); + negativeValues.add(currentNegativeStackInfo); } } + } + final int length = current.dataCount; + for (int i = 0; i < length; i++) { + final num xValue = current.xValues[i]; + num yValue = actualYValues[i]; if (currentPositiveStackInfo?.stackingValues != null) { - final int length = currentPositiveStackInfo!.stackingValues!.length; - if (length == 0 || i > length - 1) { - currentPositiveStackInfo.stackingValues!.add(0); + if (!currentPositiveStackInfo!.stackingValues.containsKey(xValue)) { + currentPositiveStackInfo.stackingValues[xValue] = 0; } } if (currentNegativeStackInfo?.stackingValues != null) { - final int length = currentNegativeStackInfo!.stackingValues!.length; - if (length == 0 || i > length - 1) { - currentNegativeStackInfo.stackingValues!.add(0); + if (!currentNegativeStackInfo!.stackingValues.containsKey(xValue)) { + currentNegativeStackInfo.stackingValues[xValue] = 0; } } if (isStacked100) { - yValue = yValue / current._percentageValues[i] * 100; + yValue = yValue / current._percentageValues[xValue]! * 100; yValue = yValue.isNaN ? 0 : yValue; } + num stackValue = 0; if (isStackedArea || yValue >= 0) { - stackValue = currentPositiveStackInfo!.stackingValues![i]; - currentPositiveStackInfo.stackingValues![i] = - (stackValue + yValue).toDouble(); + if (currentPositiveStackInfo!.stackingValues.containsKey(xValue)) { + stackValue = currentPositiveStackInfo.stackingValues[xValue]!; + currentPositiveStackInfo.stackingValues[xValue] = stackValue + yValue; + } } else { - stackValue = currentNegativeStackInfo!.stackingValues![i]; - currentNegativeStackInfo.stackingValues![i] = - (stackValue + yValue).toDouble(); + if (currentNegativeStackInfo!.stackingValues.containsKey(xValue)) { + stackValue = currentNegativeStackInfo.stackingValues[xValue]!; + currentNegativeStackInfo.stackingValues[xValue] = stackValue + yValue; + } } // Add stacking top and bottom values. @@ -6027,7 +6170,6 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer num minY = yMinimum; num maxY = yMaximum; - if (yMinimum > yMaximum) { minY = isStacked100 ? -100 : yMaximum; } @@ -6042,12 +6184,7 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer void _calculatePercentageValues(List yDependents) { StackedSeriesRenderer? current; - List<_StackingInfo>? percentageValues; - _StackingInfo? stackingInfo; - String groupName; - num stackValue; - num yValue; - + List<_StackingInfo>? percentageInfo; for (final AxisDependent yDependant in yDependents) { StackedSeriesRenderer? yDependantSeries; if (yDependant is StackedSeriesRenderer) { @@ -6071,60 +6208,64 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer final bool isContainsStackedArea = seriesType.contains('stackedarea'); final bool isContainsStackedArea100 = seriesType.contains('stackedarea100'); - groupName = + final String groupName = isContainsStackedArea100 ? 'stackedareagroup' : current.groupName; - if (percentageValues == null) { - percentageValues = <_StackingInfo>[]; - stackingInfo = _StackingInfo(groupName, []); + _StackingInfo? stackingInfo; + if (percentageInfo == null) { + percentageInfo = <_StackingInfo>[]; + stackingInfo = _StackingInfo(groupName, {}); } for (int i = 0; i < length; i++) { - yValue = current._stackYValues[i]; - if (percentageValues.isNotEmpty) { - final int percentageLength = percentageValues.length; + final num xValue = current.xValues[i]; + final num yValue = current._stackYValues[i]; + if (percentageInfo.isNotEmpty) { + final int percentageLength = percentageInfo.length; for (int k = 0; k < percentageLength; k++) { - if (groupName == percentageValues[k].groupName) { - stackingInfo = percentageValues[k]; + if (groupName == percentageInfo[k].groupName) { + stackingInfo = percentageInfo[k]; break; } else if (k == percentageLength - 1) { - stackingInfo = _StackingInfo(groupName, []); - percentageValues.add(stackingInfo); + stackingInfo = _StackingInfo(groupName, {}); + percentageInfo.add(stackingInfo); } } } if (stackingInfo?.stackingValues != null) { - final int length = stackingInfo!.stackingValues!.length; - if (length == 0 || i > length - 1) { - stackingInfo.stackingValues!.add(0); + if (!stackingInfo!.stackingValues.containsKey(xValue)) { + stackingInfo.stackingValues[xValue] = 0; } } - if (isContainsStackedArea || yValue >= 0) { - stackValue = stackingInfo!.stackingValues![i]; - stackingInfo.stackingValues![i] = (stackValue + yValue).toDouble(); - } else { - stackValue = stackingInfo!.stackingValues![i]; - stackingInfo.stackingValues![i] = (stackValue - yValue).toDouble(); + if (stackingInfo!.stackingValues.containsKey(xValue)) { + if (isContainsStackedArea || yValue >= 0) { + stackingInfo.stackingValues[xValue] = + stackingInfo.stackingValues[xValue]! + yValue; + } else { + stackingInfo.stackingValues[xValue] = + stackingInfo.stackingValues[xValue]! - yValue; + } } if (i == length - 1) { - percentageValues.add(stackingInfo); + percentageInfo.add(stackingInfo); } } - if (percentageValues.isNotEmpty) { - final int percentageLength = percentageValues.length; + if (percentageInfo.isNotEmpty) { + final int percentageLength = percentageInfo.length; for (int i = 0; i < percentageLength; i++) { if (isContainsStackedArea100) { - current._percentageValues = percentageValues[i].stackingValues!; - } else if (current.groupName == percentageValues[i].groupName) { - current._percentageValues = percentageValues[i].stackingValues!; + current._percentageValues = percentageInfo[i].stackingValues; + } else if (current.groupName == percentageInfo[i].groupName) { + current._percentageValues = percentageInfo[i].stackingValues; } } } } + percentageInfo?.clear(); } @override @@ -6157,6 +6298,7 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer ..xValues = xValues ..yLists = >[topValues] ..stackedYValues = yValues + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -6168,10 +6310,12 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); - topValues.clear(); + _resetYLists(); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); /// Calculate [StackYValues] based on empty point modes with yValues. _applyDropOrGapEmptyPointModes(this); @@ -6233,11 +6377,12 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); - topValues.clear(); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); + _resetYLists(); /// Calculate [StackYValues] based on empty point modes with yValues. _applyDropOrGapEmptyPointModes(this); @@ -6252,12 +6397,12 @@ abstract class StackedSeriesRenderer extends XyDataSeriesRenderer trendlineContainer?.populateDataSource(xValues, seriesYValues: topValues); } + @override + num trackballYValue(int index) => topValues[index]; + @override void dispose() { - topValues.clear(); - bottomValues.clear(); - _percentageValues.clear(); - _stackYValues.clear(); + _resetYLists(); super.dispose(); } } @@ -6271,8 +6416,7 @@ class _StackingInfo { String groupName; /// Holds the list of stacking values. - // ignore: prefer_final_fields - List? stackingValues; + Map stackingValues; } /// Renders the xy series. @@ -6436,12 +6580,16 @@ abstract class RangeSeriesRendererBase ChartValueMapper? highValueMapper; ChartValueMapper? lowValueMapper; - @override - void _resetDataSourceHolders() { + void _resetYLists() { highValues.clear(); lowValues.clear(); nonEmptyHighValues.clear(); nonEmptyLowValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _resetYLists(); super._resetDataSourceHolders(); } @@ -6451,19 +6599,26 @@ abstract class RangeSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (highValueMapper != null && lowValueMapper != null) { if (sortingOrder == SortingOrder.none) { super.populateDataSource( - >[highValueMapper!, lowValueMapper!], - >[highValues, lowValues], - ); + >[highValueMapper!, lowValueMapper!], + >[highValues, lowValues], + >[], + fPaths, + chaoticFLists, + fLists); } else { super.populateDataSource( >[highValueMapper!, lowValueMapper!], >[_chaoticHighValues, _chaoticLowValues], >[highValues, lowValues], + fPaths, + chaoticFLists, + fLists, ); } } @@ -6492,6 +6647,7 @@ abstract class RangeSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (highValueMapper != null && lowValueMapper != null) { @@ -6504,8 +6660,10 @@ abstract class RangeSeriesRendererBase >[highValues, lowValues], >[], fPaths, + chaoticFLists, fLists); } else { + _resetYLists(); super.updateDataPoints( removedIndexes, addedIndexes, @@ -6514,6 +6672,7 @@ abstract class RangeSeriesRendererBase >[_chaoticHighValues, _chaoticLowValues], >[highValues, lowValues], fPaths, + chaoticFLists, fLists); } } @@ -6599,6 +6758,9 @@ abstract class RangeSeriesRendererBase seriesLowValues: nonEmptyLowValues); } + @override + num trackballYValue(int index) => highValues[index]; + @override void populateChartPoints({ List? positions, @@ -6640,6 +6802,7 @@ abstract class RangeSeriesRendererBase ..xRawValues = xRawValues ..xValues = xValues ..yLists = >[highValues, lowValues] + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -6702,10 +6865,7 @@ abstract class RangeSeriesRendererBase void dispose() { _chaoticHighValues.clear(); _chaoticLowValues.clear(); - lowValues.clear(); - highValues.clear(); - nonEmptyHighValues.clear(); - nonEmptyLowValues.clear(); + _resetYLists(); super.dispose(); } } @@ -6959,13 +7119,22 @@ abstract class FinancialSeriesRendererBase } } - @override - void _resetDataSourceHolders() { + void _resetYLists() { highValues.clear(); lowValues.clear(); openValues.clear(); closeValues.clear(); volumeValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _chaoticHighValues.clear(); + _chaoticLowValues.clear(); + _chaoticOpenValues.clear(); + _chaoticCloseValues.clear(); + _chaoticVolumeValues.clear(); + _resetYLists(); super._resetDataSourceHolders(); } @@ -6975,6 +7144,7 @@ abstract class FinancialSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (highValueMapper != null && @@ -6997,7 +7167,8 @@ abstract class FinancialSeriesRendererBase ]; if (sortingOrder == SortingOrder.none) { - super.populateDataSource(mappers, finalYLists); + super.populateDataSource( + mappers, finalYLists, >[], fPaths, chaoticFLists, fLists); } else { super.populateDataSource( mappers, @@ -7009,6 +7180,9 @@ abstract class FinancialSeriesRendererBase if (volumeValueMapper != null) _chaoticVolumeValues, ], finalYLists, + fPaths, + chaoticFLists, + fLists, ); } } @@ -7050,6 +7224,7 @@ abstract class FinancialSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (highValueMapper != null && @@ -7073,8 +7248,9 @@ abstract class FinancialSeriesRendererBase if (sortingOrder == SortingOrder.none) { super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - mappers, finalYLists, >[], fPaths, fLists); + mappers, finalYLists, >[], fPaths, chaoticFLists, fLists); } else { + _resetYLists(); super.updateDataPoints( removedIndexes, addedIndexes, @@ -7089,6 +7265,7 @@ abstract class FinancialSeriesRendererBase ], finalYLists, fPaths, + chaoticFLists, fLists); } } @@ -7129,6 +7306,9 @@ abstract class FinancialSeriesRendererBase seriesHighValues: highValues, seriesLowValues: lowValues); } + @override + num trackballYValue(int index) => highValues[index]; + @override double legendIconBorderWidth() { return 2; @@ -7171,6 +7351,7 @@ abstract class FinancialSeriesRendererBase ..xRawValues = xRawValues ..xValues = xValues ..yLists = >[highValues, lowValues, openValues, closeValues] + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -7254,12 +7435,7 @@ abstract class FinancialSeriesRendererBase _chaoticOpenValues.clear(); _chaoticCloseValues.clear(); _chaoticVolumeValues.clear(); - - highValues.clear(); - lowValues.clear(); - openValues.clear(); - closeValues.clear(); - volumeValues.clear(); + _resetYLists(); super.dispose(); } } @@ -7990,7 +8166,10 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer final List yValues = []; final List _chaoticYValues = []; final List pointRadii = []; + final List _chaoticPointRadii = []; + final List dataLabelValues = []; + final List _chaoticDataLabelValues = []; List circularXValues = []; List circularYValues = []; @@ -8162,15 +8341,20 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer super.attach(owner); } - @override - void _resetDataSourceHolders() { + void _resetYLists() { yValues.clear(); - _chaoticYValues.clear(); circularXValues.clear(); circularYValues.clear(); + groupingDataLabelValues.clear(); + } + + @override + void _resetDataSourceHolders() { + _chaoticYValues.clear(); pointRadii.clear(); + _chaoticDataLabelValues.clear(); dataLabelValues.clear(); - groupingDataLabelValues.clear(); + _resetYLists(); super._resetDataSourceHolders(); } @@ -8180,6 +8364,7 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -8187,10 +8372,6 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer chaoticYLists = >[]; yLists = >[]; } - if (fPaths == null) { - fPaths = >[]; - fLists = >[]; - } if (yValueMapper != null) { yPaths.add(yValueMapper!); @@ -8202,22 +8383,50 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer } } - if (pointRadiusMapper != null) { - fPaths.add(pointRadiusMapper!); - fLists?.add(pointRadii); + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; } - if (dataLabelMapper != null) { - fPaths.add(dataLabelMapper!); - fLists?.add(dataLabelValues); - } + _addPointRadiusMapper(fPaths, chaoticFLists, fLists); + _addDataLabelMapper(fPaths, chaoticFLists, fLists); - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); _calculateGroupingValues(); markNeedsLegendUpdate(); populateChartPoints(); } + void _addPointRadiusMapper(List>? fPaths, + List>? chaoticFLists, List>? fLists) { + if (fPaths != null && pointRadiusMapper != null) { + fPaths.add(pointRadiusMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(pointRadii); + } else { + pointRadii.clear(); + chaoticFLists?.add(_chaoticPointRadii); + fLists?.add(pointRadii); + } + } + } + + void _addDataLabelMapper(List>? fPaths, + List>? chaoticFLists, List>? fLists) { + if (fPaths != null && dataLabelMapper != null) { + fPaths.add(dataLabelMapper!); + if (sortingOrder == SortingOrder.none) { + chaoticFLists?.add(dataLabelValues); + } else { + dataLabelValues.clear(); + chaoticFLists?.add(_chaoticDataLabelValues); + fLists?.add(dataLabelValues); + } + } + } + @override void populateChartPoints({ List? positions, @@ -8243,6 +8452,7 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -8256,12 +8466,23 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer if (sortingOrder == SortingOrder.none) { chaoticYLists?.add(yValues); } else { + _resetYLists(); chaoticYLists?.add(_chaoticYValues); yLists?.add(yValues); } } + + if (fPaths == null) { + fPaths = >[]; + chaoticFLists = >[]; + fLists = >[]; + } + + _addPointRadiusMapper(fPaths, chaoticFLists, fLists); + _addDataLabelMapper(fPaths, chaoticFLists, fLists); + super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); _calculateGroupingValues(); } @@ -8280,6 +8501,7 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer ..xRawValues = circularXValues ..xValues = xValues ..yLists = >[circularYValues] + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -8347,7 +8569,7 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer for (int i = 0; i < dataCount; i++) { bool isVisible = true; - if (_segments.isNotEmpty) { + if (_segments.isNotEmpty && i < _segments.length) { isVisible = _segments[i].isVisible; } firstVisibleIndex = @@ -8800,6 +9022,7 @@ abstract class CircularSeriesRenderer extends ChartSeriesRenderer @override void dispose() { renderDataLabelRegions.clear(); + _resetDataSourceHolders(); super.dispose(); } } @@ -8810,14 +9033,15 @@ abstract class BoxAndWhiskerSeriesRendererBase SbsSeriesMixin, ClusterSeriesMixin, CartesianRealTimeUpdateMixin { - final List> chaoticYValues = >[]; - final List> yValues = >[]; + final List> _chaoticYValues = >[]; + List> yValues = >[]; ChartValueMapper>? yValueMapper; @override void _resetDataSourceHolders() { - chaoticYValues.clear(); + _chaoticYValues.clear(); + yValues.clear(); super._resetDataSourceHolders(); } @@ -8827,6 +9051,7 @@ abstract class BoxAndWhiskerSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { _resetDataSourceHolders(); @@ -8839,10 +9064,11 @@ abstract class BoxAndWhiskerSeriesRendererBase if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); final int length = dataSource!.length; final int fPathLength = fPaths.length; @@ -8867,13 +9093,13 @@ abstract class BoxAndWhiskerSeriesRendererBase addXValue(rawX, currentX); xMinimum = min(xMinimum, currentX); xMaximum = max(xMaximum, currentX); - if (_hasLinearData) { - _hasLinearData = currentX >= previousX; + if (_hasLinearDataSource) { + _hasLinearDataSource = currentX >= previousX; } final List? yData = yValueMapper!(current, i); if (yData == null) { - chaoticYValues.add([]); + _chaoticYValues.add([]); } else { num minY = double.infinity; num maxY = double.negativeInfinity; @@ -8888,7 +9114,7 @@ abstract class BoxAndWhiskerSeriesRendererBase } } nonNullYValues.sort(); - chaoticYValues.add(nonNullYValues); + _chaoticYValues.add(nonNullYValues); yMinimum = min(yMinimum, minY); yMaximum = max(yMaximum, maxY); } @@ -8896,7 +9122,7 @@ abstract class BoxAndWhiskerSeriesRendererBase for (int j = 0; j < fPathLength; j++) { final ChartValueMapper fPath = fPaths[j]; final Object? fValue = fPath(current, i); - fLists![j].add(fValue); + chaoticFLists![j].add(fValue); } previousX = currentX; @@ -8907,48 +9133,76 @@ abstract class BoxAndWhiskerSeriesRendererBase yMin = yMinimum; yMax = yMaximum; _dataCount = _chaoticXValues.length; - _doSortingIfNeeded(chaoticYLists, yLists); + _canFindLinearVisibleIndexes = _hasLinearDataSource; + _doSortingIfNeeded(chaoticYLists, yLists, chaoticFLists, fLists); populateChartPoints(); + _updateXValuesForCategoryTypeAxes(); } @override void _doSortingIfNeeded( - List>? chaoticYLists, List>? yLists) { - if (sortingOrder != SortingOrder.none && chaoticYValues.isNotEmpty) { - if (_sortValues.isEmpty) { + List>? chaoticYLists, + List>? yLists, + List>? chaoticFLists, + List>? fLists) { + if (sortingOrder != SortingOrder.none && _chaoticYValues.isNotEmpty) { + if (_chaoticRawSortValues.isEmpty) { if (_chaoticRawXValues.isNotEmpty) { - _sortValues.addAll(_chaoticRawXValues); + _chaoticRawSortValues.addAll(_chaoticRawXValues); } else { - _sortValues.addAll(_chaoticXValues); + _chaoticRawSortValues.addAll(_chaoticXValues); } } switch (sortingOrder) { case SortingOrder.ascending: - _sortBoxValues(); + _sortBoxValues(chaoticFLists, fLists); break; case SortingOrder.descending: - _sortBoxValues(ascending: false); + _sortBoxValues(chaoticFLists, fLists, ascending: false); break; case SortingOrder.none: break; } } else { - xValues = _chaoticXValues; - xRawValues = _chaoticRawXValues; + xValues.clear(); + xValues.addAll(_chaoticXValues); + xRawValues.clear(); + xRawValues.addAll(_chaoticRawXValues); + yValues.clear(); + yValues.addAll(_chaoticYValues); } } - void _sortBoxValues({bool ascending = true}) { - final List orderedIndexes = _sortedIndexes(ascending); - final void Function(int index) copyX = - _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; - if (orderedIndexes.isNotEmpty) { - for (final int sortedIndex in orderedIndexes) { - copyX(sortedIndex); - yValues.add(chaoticYValues[sortedIndex]); + void _sortBoxValues( + List>? chaoticFLists, List>? fLists, + {bool ascending = true}) { + _computeSortedIndexes(ascending); + if (sortedIndexes.isNotEmpty) { + final void Function(int index, num xValue) copyX = + _chaoticRawXValues.isNotEmpty ? _copyXAndRawXValue : _copyXValue; + final int fLength = fLists!.length; + final int length = sortedIndexes.length; + + for (int i = 0; i < length; i++) { + final int sortedIndex = sortedIndexes[i]; + final num xValue = _chaoticXValues[sortedIndex]; + copyX(sortedIndex, xValue); + yValues.add(_chaoticYValues[sortedIndex]); + + for (int k = 0; k < fLength; k++) { + final List fValues = fLists[k]; + final List chaoticFValues = chaoticFLists![k]; + fValues.add(chaoticFValues[sortedIndex]); + } + + // During sorting, determine data is linear or non-linear for + // calculating visibleIndexes for proper axis range & segment rendering. + if (_canFindLinearVisibleIndexes) { + _canFindLinearVisibleIndexes = isValueLinear(i, xValue, xValues); + } } } } @@ -8957,7 +9211,7 @@ abstract class BoxAndWhiskerSeriesRendererBase DoubleRange _calculateYRange({List>? yLists}) { num minimum = double.infinity; num maximum = double.negativeInfinity; - for (final List yList in chaoticYValues) { + for (final List yList in _chaoticYValues) { for (final num yValue in yList) { minimum = min(yMin, yValue); maximum = max(yMax, yValue); @@ -8975,6 +9229,7 @@ abstract class BoxAndWhiskerSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (dataSource == null || @@ -8986,34 +9241,45 @@ abstract class BoxAndWhiskerSeriesRendererBase if (fPaths == null) { fPaths = >[]; + chaoticFLists = >[]; fLists = >[]; } - _addPointColorMapper(fPaths, fLists); - _addSortValueMapper(fPaths, fLists); + _addPointColorMapper(fPaths, chaoticFLists, fLists); + _addSortValueMapper(fPaths, chaoticFLists, fLists); if (removedIndexes != null) { - _removeDataPoints( - removedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _removeDataPoints(removedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (addedIndexes != null) { - _addDataPoints( - addedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _addDataPoints(addedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } if (replacedIndexes != null) { - _replaceDataPoints( - replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(replacedIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); + } + + // During sorting, the x, y, and feature path values are recalculated. + // Therefore, it is necessary to clear the old values and update these lists + // with the newly recalculated values. + if (sortingOrder != SortingOrder.none) { + xValues.clear(); + xRawValues.clear(); + yValues.clear(); } - _applyEmptyPointModeIfNeeded(chaoticYValues); - _doSortingIfNeeded(chaoticYValues, yLists); + _applyEmptyPointModeIfNeeded(_chaoticYValues); + _doSortingIfNeeded(_chaoticYValues, yLists, chaoticFLists, fLists); final DoubleRange xRange = _findMinMaxXRange(xValues); - final DoubleRange yRange = _findMinMaxYRange(chaoticYValues); + final DoubleRange yRange = _findMinMaxYRange(_chaoticYValues); _updateAxisRange( xRange.minimum, xRange.maximum, yRange.minimum, yRange.maximum); computeNonEmptyYValues(); _populateTrendlineDataSource(); + _updateXValuesForCategoryTypeAxes(); canUpdateOrCreateSegments = true; markNeedsLayout(); @@ -9026,23 +9292,25 @@ abstract class BoxAndWhiskerSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final int fPathLength = fPaths?.length ?? 0; for (final int index in indexes) { _removeXValueAt(index); - chaoticYValues.removeAt(index); + _removeRawSortValueAt(index); + _chaoticYValues.removeAt(index); for (int k = 0; k < fPathLength; k++) { - fLists![k].removeAt(index); + chaoticFLists![k].removeAt(index); } } _dataCount = _chaoticXValues.length; // Collecting previous and next index to update them. final List mutableIndexes = _findMutableIndexes(indexes); - _replaceDataPoints( - mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, fLists); + _replaceDataPoints(mutableIndexes, yPaths, chaoticYLists, yLists, fPaths, + chaoticFLists, fLists); } @override @@ -9052,12 +9320,15 @@ abstract class BoxAndWhiskerSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final int fPathLength = fPaths?.length ?? 0; final Function(int, D) preferredXValue = _preferredXValue(); final Function(int, D?, num) insertXValue = _insertXValueIntoRawAndChaoticXLists; + final Function(int, D?) insertRawSortValue = + _insertRawXValueIntoChaoticRawSortValue; num xMinimum = double.infinity; num xMaximum = double.negativeInfinity; @@ -9074,15 +9345,16 @@ abstract class BoxAndWhiskerSeriesRendererBase final num currentX = preferredXValue(index, rawX); insertXValue(index, rawX, currentX); + insertRawSortValue(index, rawX); xMinimum = min(xMinimum, currentX); xMaximum = max(xMaximum, currentX); - if (_hasLinearData) { - _hasLinearData = _isValueLinear(index, currentX, _chaoticXValues); + if (_hasLinearDataSource) { + _hasLinearDataSource = isValueLinear(index, currentX, _chaoticXValues); } final List? yData = yValueMapper!(current, index); if (yData == null) { - chaoticYValues.add([]); + _chaoticYValues.add([]); } else { num minY = double.infinity; num maxY = double.negativeInfinity; @@ -9097,18 +9369,19 @@ abstract class BoxAndWhiskerSeriesRendererBase } } nonNullYValues.sort(); - chaoticYValues.insert(index, nonNullYValues); + _chaoticYValues.insert(index, nonNullYValues); yMinimum = min(yMinimum, minY); yMaximum = max(yMaximum, maxY); } for (int j = 0; j < fPathLength; j++) { final Object? fValue = fPaths![j](current, j); - fLists![j].insert(index, fValue); + chaoticFLists![j].insert(index, fValue); } } _dataCount = _chaoticXValues.length; + _canFindLinearVisibleIndexes = _hasLinearDataSource; } @override @@ -9118,6 +9391,7 @@ abstract class BoxAndWhiskerSeriesRendererBase List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ) { final Function(int, D) preferredXValue = _preferredXValue(); @@ -9127,44 +9401,46 @@ abstract class BoxAndWhiskerSeriesRendererBase final int fPathLength = fPaths?.length ?? 0; for (final int index in indexes) { - if (index < dataSource!.length - 1) { - final T current = dataSource![index]; - final D? rawX = xValueMapper!(current, index); - if (_xNullPointIndexes.contains(index)) { - _xNullPointIndexes.remove(index); - } + if (index < 0 || index >= dataSource!.length) { + continue; + } - if (rawX == null) { - _xNullPointIndexes.add(index); - continue; - } + final T current = dataSource![index]; + final D? rawX = xValueMapper!(current, index); + if (_xNullPointIndexes.contains(index)) { + _xNullPointIndexes.remove(index); + } + + if (rawX == null) { + _xNullPointIndexes.add(index); + continue; + } - final num currentX = preferredXValue(index, rawX); - replaceXValue(index, rawX, currentX); + final num currentX = preferredXValue(index, rawX); + replaceXValue(index, rawX, currentX); - final List? yData = yValueMapper!(current, index); - if (yData == null) { - chaoticYValues.add([]); - } else { - num minY = double.infinity; - num maxY = double.negativeInfinity; - final List nonNullYValues = []; - final int yLength = yData.length; - for (int j = 0; j < yLength; j++) { - final num? yVal = yData[j]; - if (yVal != null && !yVal.isNaN) { - nonNullYValues.add(yVal); - minY = min(minY, yVal); - maxY = max(maxY, yVal); - } + final List? yData = yValueMapper!(current, index); + if (yData == null) { + _chaoticYValues.add([]); + } else { + num minY = double.infinity; + num maxY = double.negativeInfinity; + final List nonNullYValues = []; + final int yLength = yData.length; + for (int j = 0; j < yLength; j++) { + final num? yVal = yData[j]; + if (yVal != null && !yVal.isNaN) { + nonNullYValues.add(yVal); + minY = min(minY, yVal); + maxY = max(maxY, yVal); } - nonNullYValues.sort(); - chaoticYValues[index] = nonNullYValues; } + nonNullYValues.sort(); + _chaoticYValues[index] = nonNullYValues; + } - for (int j = 0; j < fPathLength; j++) { - fLists![j][index] = fPaths![j](current, j); - } + for (int j = 0; j < fPathLength; j++) { + chaoticFLists![j][index] = fPaths![j](current, j); } } } @@ -9200,4 +9476,11 @@ abstract class BoxAndWhiskerSeriesRendererBase return _calculateYPosition(x, y, ChartDataLabelAlignment.bottom, size); } } + + @override + void dispose() { + _chaoticYValues.clear(); + yValues.clear(); + super.dispose(); + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart index 90f39bc99..881bbcbf5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/column_series.dart @@ -2,11 +2,14 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; +import '../common/callbacks.dart'; import '../common/chart_point.dart'; +import '../common/core_legend.dart'; import '../common/core_tooltip.dart'; +import '../common/legend.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -357,45 +360,33 @@ class ColumnSeriesRenderer extends XyDataSeriesRenderer } @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; + void handleLegendItemTapped(LegendItem item, bool isToggled) { + if (parent != null && parent!.onLegendTapped != null) { + final ChartLegendItem legendItem = item as ChartLegendItem; + final LegendTapArgs args = LegendTapArgs( + legendItem.series, legendItem.seriesIndex, legendItem.pointIndex); + parent!.onLegendTapped!(args); } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is ColumnSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; + parent!.behaviorArea?.hideInteractiveTooltip(); + + controller.isVisible = !isToggled; + if (controller.isVisible == !isToggled) { + item.onToggled?.call(); + visibilityBeforeTogglingLegend = isToggled; + animateAllBarSeries(parent!); + } + + if (trendlineContainer != null) { + trendlineContainer!.updateLegendState(item, isToggled); + markNeedsLegendUpdate(); } - return segmentCollection; + markNeedsUpdate(); + } + + @override + void onRealTimeAnimationUpdate() { + super.onRealTimeAnimationUpdate(); + transformValues(); } } @@ -426,12 +417,7 @@ class ColumnSegment extends ChartSegment with BarSeriesTrackerMixin { return; } - if (series.animationDuration > 0) { - _oldSegmentRect = - RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); - } else { - _oldSegmentRect = segmentRect; - } + _oldSegmentRect = segmentRect; } @override @@ -530,21 +516,20 @@ class ColumnSegment extends ChartSegment with BarSeriesTrackerMixin { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, y), series.pointToPixelY(x, y)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; @@ -564,15 +549,21 @@ class ColumnSegment extends ChartSegment with BarSeriesTrackerMixin { @override void onPaint(Canvas canvas) { - // Draws the tracker bounds. - super.onPaint(canvas); + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } if (segmentRect == null) { return; } final RRect? paintRRect = - RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + series.parent!.isLegendToggled && _oldSegmentRect != null + ? performLegendToggleAnimation( + series, segmentRect!, _oldSegmentRect!, series.borderRadius) + : RRect.lerp(_oldSegmentRect, segmentRect, animationFactor); + if (paintRRect == null || paintRRect.isEmpty) { return; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart index 5f5a3f286..d5630e8ba 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/doughnut_series.dart @@ -476,20 +476,17 @@ class DoughnutSegment extends ChartSegment { void transformValues() { fillPath.reset(); - double startAngle; - double endAngle; - double degree; - - if (_priorEndAngle.isNaN) { - final int seriesStartAngle = series.startAngle - 90; - degree = _degree * animationFactor; - startAngle = lerpDouble(seriesStartAngle, _startAngle, animationFactor)!; - endAngle = startAngle + degree; - } else { - startAngle = lerpDouble(_priorStartAngle, _startAngle, animationFactor)!; - endAngle = lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; - degree = endAngle - startAngle; - } + double degree = _degree * animationFactor; + final double angle = calculateAngle( + series.animationFactor == 1, series.startAngle, series.endAngle); + final double startAngle = lerpDouble( + _priorEndAngle.isNaN ? angle : _priorStartAngle, + _startAngle, + animationFactor)!; + final double endAngle = _priorEndAngle.isNaN + ? startAngle + degree + : lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; + degree = _priorEndAngle.isNaN ? degree : endAngle - startAngle; // If the startAngle and endAngle value is same, then degree will be 0. // Hence no need to render segments. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart index 9712d598f..03726a5d5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/error_bar_series.dart @@ -454,9 +454,11 @@ class ErrorBarSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); _xMin = xMin; _xMax = xMax; _yMin = yMin; @@ -472,9 +474,10 @@ class ErrorBarSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists]) { super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); _xMin = xMin; _xMax = xMax; _yMin = yMin; @@ -867,6 +870,10 @@ class ErrorBarSegment extends ChartSegment { @override void transformValues() { + if (_xValues.isEmpty || _yValues.isEmpty || _errorBarValues.isEmpty) { + return; + } + _verticalPath.reset(); _verticalCapPath.reset(); _horizontalPath.reset(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart index 4a36bcbab..281c5cf8e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/fast_line_series.dart @@ -5,11 +5,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../axis/axis.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -119,47 +119,6 @@ class FastLineSeriesRenderer extends XyDataSeriesRenderer ? ShapeMarkerType.fastLineSeriesWithDashArray : ShapeMarkerType.fastLineSeries; - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is FastLineSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -233,7 +192,7 @@ class FastLineSegment extends ChartSegment { void transformValues() { points.clear(); _drawIndexes.clear(); - if (series.hasLinearData) { + if (series.canFindLinearVisibleIndexes) { _linearPoints(); } else { _nonLinearPoints(); @@ -409,22 +368,26 @@ class FastLineSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { final int nearestPointIndex = _findNearestPoint(points, position); if (nearestPointIndex != -1) { - final Offset position = points[nearestPointIndex]; - if (position.isNaN) { + final Offset preferredPos = points[nearestPointIndex]; + if (preferredPos.isNaN) { return null; } final int actualPointIndex = _drawIndexes[nearestPointIndex]; final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: actualPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; @@ -435,7 +398,8 @@ class FastLineSegment extends ChartSegment { num? nearPointX; num? nearPointY; int? pointIndex; - for (int i = 0; i < points.length; i++) { + final int length = points.length; + for (int i = 0; i < length; i++) { nearPointX ??= series.isTransposed ? series.xAxis!.visibleRange!.minimum : points[0].dx; @@ -497,7 +461,7 @@ class FastLineSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - if (points == null || points.isEmpty) { + if (points.isEmpty) { return; } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart index 40016bd2c..d3d416eb1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/funnel_series.dart @@ -1001,6 +1001,7 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -1008,10 +1009,7 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer chaoticYLists = >[]; yLists = >[]; } - if (fPaths == null) { - fPaths = >[]; - fLists = >[]; - } + if (yValueMapper != null) { yPaths.add(yValueMapper!); if (sortingOrder == SortingOrder.none) { @@ -1021,7 +1019,8 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer yLists?.add(yValues); } } - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); markNeedsLegendUpdate(); populateChartPoints(); } @@ -1051,6 +1050,7 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -1069,7 +1069,7 @@ class FunnelSeriesRenderer extends ChartSeriesRenderer } } super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); } @override diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart index 0684aa9c1..ec978dceb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_open_close_series.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../common/element_widget.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -190,9 +190,9 @@ class HiloOpenCloseSeriesRenderer final SfChartThemeData chartThemeData = parent!.chartThemeData!; final ThemeData themeData = parent!.themeData!; if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { - return chartThemeData.plotAreaBackgroundColor; + return chartThemeData.plotAreaBackgroundColor!; } else if (chartThemeData.backgroundColor != Colors.transparent) { - return chartThemeData.backgroundColor; + return chartThemeData.backgroundColor!; } return themeData.colorScheme.surface; } @@ -222,52 +222,14 @@ class HiloOpenCloseSeriesRenderer final int index = segment.currentSegmentIndex; final bool isBull = closeValues[index] > openValues[index]; final Color color = isBull ? bullColor : bearColor; - final Color? segmentColor = pointColorMapper != null ? null : color; + final Color? segmentColor = pointColorMapper != null && + pointColors[segment.currentSegmentIndex] != null + ? null + : color; updateSegmentColor(segment, segmentColor, borderWidth, fillColor: segmentColor, isLineType: true); updateSegmentGradient(segment); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is HiloOpenCloseSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for hilo open close series. @@ -445,20 +407,27 @@ class HiloOpenCloseSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final num left = x + series.sbsInfo.minimum; - final num right = x + series.sbsInfo.maximum; - final CartesianChartPoint chartPoint = _chartPoint(); - return ChartTrackballInfo( - position: points[0], - point: chartPoint, - series: series, - pointIndex: currentSegmentIndex, - seriesIndex: series.index, - lowYPos: series.pointToPixelY((right + left) / 2, low), - highYPos: series.pointToPixelY((right + left) / 2, high), - highXPos: series.pointToPixelX((right + left) / 2, high), - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = + Offset(series.pointToPixelX(x, high), series.pointToPixelY(x, high)); + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(x, low), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; } /// Gets the color of the series. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart index a648b4bf3..451aa843c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/hilo_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -171,47 +171,6 @@ class HiloSeriesRenderer extends RangeSeriesRendererBase updateSegmentColor(segment, color, borderWidth, isLineType: true); updateSegmentGradient(segment); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is HiloSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for hilo series. @@ -380,18 +339,25 @@ class HiloSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final CartesianChartPoint chartPoint = _chartPoint(); - return ChartTrackballInfo( - position: points[0], - point: chartPoint, - series: series, - pointIndex: currentSegmentIndex, - seriesIndex: series.index, - lowYPos: series.pointToPixelY(xValue, low), - highYPos: series.pointToPixelY(xValue, high), - highXPos: series.pointToPixelX(xValue, high), - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final CartesianChartPoint chartPoint = _chartPoint(); + return ChartTrackballInfo( + position: points[0], + highXPos: series.pointToPixelX(xValue, high), + highYPos: series.pointToPixelY(xValue, high), + lowYPos: series.pointToPixelY(xValue, low), + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); + } + return null; } /// Gets the color of the series. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart index 334e2171b..4491a8e11 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/histogram_series.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -537,6 +537,7 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { _resetDataHolders(); @@ -565,7 +566,8 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer // Invoke custom [_histogramXValueMapper] method. xValueMapper = _histogramXValueMapper; - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); populateChartPoints(); } @@ -609,6 +611,9 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer } } + @override + num trackballYValue(int index) => _histogramYValues[index]; + @override void setData(int index, ChartSegment segment) { super.setData(index, segment); @@ -646,48 +651,6 @@ class HistogramSeriesRenderer extends XyDataSeriesRenderer transformValues(); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is HistogramSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void transformValues() { super.transformValues(); @@ -884,21 +847,20 @@ class HistogramSegment extends ChartSegment with BarSeriesTrackerMixin { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, y), series.pointToPixelY(x, y)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart index 5f0d8a4cc..56bbadf8f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/line_series.dart @@ -4,11 +4,11 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -88,6 +88,8 @@ class LineSeriesRenderer extends XyDataSeriesRenderer return 3; } + bool _hasNewSegmentAdded = false; + @override void setData(int index, ChartSegment segment) { super.setData(index, segment); @@ -115,7 +117,10 @@ class LineSeriesRenderer extends XyDataSeriesRenderer /// Creates a segment for a data point in the series. @override - LineSegment createSegment() => LineSegment(); + LineSegment createSegment() { + _hasNewSegmentAdded = true; + return LineSegment(); + } @override ShapeMarkerType effectiveLegendIconType() => dashArray != null @@ -129,47 +134,6 @@ class LineSeriesRenderer extends XyDataSeriesRenderer updateSegmentGradient(segment); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is LineSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -181,6 +145,9 @@ class LineSeriesRenderer extends XyDataSeriesRenderer paintMarkers(context, offset); paintDataLabels(context, offset); paintTrendline(context, offset); + if (animationController!.isCompleted) { + _hasNewSegmentAdded = false; + } } } @@ -301,48 +268,25 @@ class LineSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final int segmentIndex = nearestPointIndex == 0 - ? currentSegmentIndex - : currentSegmentIndex + 1; - final int pointIndex = clampInt(segmentIndex, 0, series.dataCount - 1); - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); - return ChartTrackballInfo( - position: points[nearestPointIndex], - point: chartPoint, - series: series, - pointIndex: currentSegmentIndex, - seriesIndex: series.index, - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; } - return null; - } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= points[0].dx; - nearPointY ??= series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final double curX = points[i].dx; - final double curY = points[i].dy; - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - return pointIndex ?? -1; + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); } CartesianChartPoint _chartPoint(int pointIndex) { @@ -380,8 +324,28 @@ class LineSegment extends ChartSegment { final Offset start; final Offset end; if (animationFactor < 1) { - start = Offset.lerp(_oldPoints[0], points[0], animationFactor)!; - end = Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + if (series.animationType == AnimationType.realtime && + series._hasNewSegmentAdded && + !isEmpty && + series.dataCount == currentSegmentIndex + 2) { + final double prevX = _oldPoints[0].dx; + final double prevY = _oldPoints[0].dy; + final double x1 = points[0].dx; + final double y1 = points[0].dy; + final double x2 = points[1].dx; + final double y2 = points[1].dy; + + final double newX1 = prevX + (x1 - prevX) * animationFactor; + final double newY1 = prevY + (y1 - prevY) * animationFactor; + final double newX2 = newX1 + (x2 - newX1) * animationFactor; + final double newY2 = newY1 + (y2 - newY1) * animationFactor; + + start = Offset(newX1, newY1); + end = Offset(newX2, newY2); + } else { + start = Offset.lerp(_oldPoints[0], points[0], animationFactor)!; + end = Offset.lerp(_oldPoints[1], points[1], animationFactor)!; + } } else { start = points[0]; end = points[1]; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart index 17d0f8bd8..badf12bdc 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/pie_series.dart @@ -446,6 +446,17 @@ class PieSeriesRenderer extends CircularSeriesRenderer { ? Position.right : Position.left; } + + @override + void onPaint(PaintingContext context, Offset offset) { + context.canvas.save(); + context.canvas.translate(center.dx, center.dy); + context.canvas.scale(animationFactor); + context.canvas.translate(-center.dx, -center.dy); + paintSegments(context, offset); + context.canvas.restore(); + paintDataLabels(context, offset); + } } class PieSegment extends ChartSegment { @@ -477,23 +488,17 @@ class PieSegment extends ChartSegment { void transformValues() { fillPath.reset(); - double startAngle; - double endAngle; - double degree; - double outerRadius; - - if (_priorEndAngle.isNaN) { - final int seriesStartAngle = series.startAngle - 90; - degree = _degree * animationFactor; - outerRadius = _outerRadius * animationFactor; - startAngle = lerpDouble(seriesStartAngle, _startAngle, animationFactor)!; - endAngle = startAngle + degree; - } else { - startAngle = lerpDouble(_priorStartAngle, _startAngle, animationFactor)!; - endAngle = lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; - degree = endAngle - startAngle; - outerRadius = _outerRadius; - } + double degree = _degree * animationFactor; + final double angle = calculateAngle( + series.animationFactor == 1, series.startAngle, series.endAngle); + final double startAngle = lerpDouble( + _priorEndAngle.isNaN ? angle : _priorStartAngle, + _startAngle, + animationFactor)!; + final double endAngle = _priorEndAngle.isNaN + ? startAngle + degree + : lerpDouble(_priorEndAngle, _endAngle, animationFactor)!; + degree = _priorEndAngle.isNaN ? degree : endAngle - startAngle; // If the startAngle and endAngle value is same, then degree will be 0. // Hence no need to render segments. @@ -510,7 +515,7 @@ class PieSegment extends ChartSegment { } fillPath = calculateArcPath( - _innerRadius, outerRadius, _center, startAngle, endAngle, degree, + _innerRadius, _outerRadius, _center, startAngle, endAngle, degree, isAnimate: true); } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart index 2fc3ad787..d00ef6a43 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/pyramid_series.dart @@ -955,6 +955,7 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -962,10 +963,7 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer chaoticYLists = >[]; yLists = >[]; } - if (fPaths == null) { - fPaths = >[]; - fLists = >[]; - } + if (yValueMapper != null) { yPaths.add(yValueMapper!); if (sortingOrder == SortingOrder.none) { @@ -975,7 +973,8 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer yLists?.add(yValues); } } - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); markNeedsLegendUpdate(); populateChartPoints(); } @@ -1005,6 +1004,7 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { if (yPaths == null) { @@ -1023,7 +1023,7 @@ class PyramidSeriesRenderer extends ChartSeriesRenderer } } super.updateDataPoints(removedIndexes, addedIndexes, replacedIndexes, - yPaths, chaoticYLists, yLists, fPaths, fLists); + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); } @override diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart index 2b0929872..55f3c18c0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_area_series.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -175,59 +175,6 @@ class RangeAreaSeriesRenderer extends RangeSeriesRendererBase borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is RangeAreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -252,6 +199,7 @@ class RangeAreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _oldHighPoints = []; @@ -262,6 +210,7 @@ class RangeAreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); return; @@ -311,6 +260,7 @@ class RangeAreaSegment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); if (_xValues.isEmpty || _lowValues.isEmpty || _highValues.isEmpty) { @@ -321,20 +271,7 @@ class RangeAreaSegment extends ChartSegment { _strokePath.reset(); _calculatePoints(_xValues, _highValues, _lowValues); - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - - switch (series.borderDrawMode) { - case RangeAreaBorderMode.all: - _strokePath = _fillPath; - break; - case RangeAreaBorderMode.excludeSides: - _createStrokePathForExcludeSides( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - } + _createFillPath(_fillPath, _highPoints, _lowPoints); } List _lerpPoints(List oldPoints, List newPoints) { @@ -381,6 +318,7 @@ class RangeAreaSegment extends ChartSegment { topY = bottomY = double.nan; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); _highPoints.add(highPoint); final Offset lowPoint = @@ -391,12 +329,36 @@ class RangeAreaSegment extends ChartSegment { } length = _oldHighPoints.length; - if (points.length > length) { + if (_highPoints.length > length) { _oldHighPoints.addAll(_highPoints.sublist(length)); _oldLowPoints.addAll(_lowPoints.sublist(length)); } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case RangeAreaBorderMode.all: + _strokePath = _fillPath; + break; + case RangeAreaBorderMode.excludeSides: + _createStrokePathForExcludeSides( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + } + } + Path _createFillPath( Path source, List highPoints, List lowPoints) { Path? path; @@ -502,7 +464,13 @@ class RangeAreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; final num high = chartPoint.high!; final double dx = series.pointToPixelX(x, high); @@ -520,13 +488,13 @@ class RangeAreaSegment extends ChartSegment { header: series.parent!.tooltipBehavior!.shared ? series.tooltipHeaderText(chartPoint) : series.name, - data: series.dataSource![currentSegmentIndex], + data: series.dataSource![pointIndex], point: chartPoint, series: series.widget, renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: currentSegmentIndex, + pointIndex: actualPointIndex, hasMultipleYValues: true, markerColors: [fillPaint.color], markerType: marker.type, @@ -536,70 +504,33 @@ class RangeAreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final num x = _xValues[nearestPointIndex]; - final num high = _highValues[nearestPointIndex]; - final num low = _lowValues[nearestPointIndex]; - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && _highPoints.isNotEmpty) { + final Offset preferredPos = _highPoints[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, chartPoint.low!), point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, - lowYPos: series.pointToPixelY(x, low), - highYPos: series.pointToPixelY(x, high), - highXPos: series.pointToPixelX(x, high), + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { final Offset a = points[i]; @@ -629,6 +560,8 @@ class RangeAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -646,6 +579,7 @@ class RangeAreaSegment extends ChartSegment { _strokePath.reset(); points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); super.dispose(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart index 07912fc14..832c36c5a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/range_column_series.dart @@ -3,12 +3,12 @@ import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -334,9 +334,9 @@ class RangeColumnSeriesRenderer extends RangeSeriesRendererBase final SfChartThemeData chartThemeData = parent!.chartThemeData!; final ThemeData themeData = parent!.themeData!; if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { - return chartThemeData.plotAreaBackgroundColor; + return chartThemeData.plotAreaBackgroundColor!; } else if (chartThemeData.backgroundColor != Colors.transparent) { - return chartThemeData.backgroundColor; + return chartThemeData.backgroundColor!; } return themeData.colorScheme.surface; } @@ -389,48 +389,6 @@ class RangeColumnSeriesRenderer extends RangeSeriesRendererBase gradient: gradient, borderGradient: borderGradient); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is RangeColumnSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for range column series. @@ -556,26 +514,24 @@ class RangeColumnSegment extends ChartSegment with BarSeriesTrackerMixin { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { - final num left = x + series.sbsInfo.minimum; - final num right = x + series.sbsInfo.maximum; + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); + final Offset preferredPos = + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, bottom), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, - lowYPos: series.pointToPixelY((left + right) / 2, bottom), - highYPos: series.pointToPixelY((left + right) / 2, top), - highXPos: series.pointToPixelX((left + right) / 2, top), + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart index 140e306cc..4ed248176 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/scatter_series.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/data_label.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; import 'chart_series.dart'; @@ -151,48 +151,6 @@ class ScatterSeriesRenderer extends XyDataSeriesRenderer return segment.getFillPaint().color; } } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is ScatterSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Creates the segments for scatter series. @@ -288,15 +246,19 @@ class ScatterSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( position: segmentRect!.center, point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: currentSegmentIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart index 5310ed3e9..a7caef604 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/spline_series.dart @@ -7,11 +7,11 @@ import 'package:syncfusion_flutter_core/core.dart'; import '../axis/axis.dart'; import '../axis/datetime_axis.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -601,46 +601,6 @@ class SplineSeriesRenderer extends XyDataSeriesRenderer updateSegmentGradient(segment); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is SplineSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { final Rect clip = clipRect(paintBounds, animationFactor, @@ -830,66 +790,25 @@ class SplineSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final int segmentIndex = nearestPointIndex == 0 - ? currentSegmentIndex - : currentSegmentIndex + 1; - final int pointIndex = clampInt(segmentIndex, 0, series.dataCount - 1); - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); - return ChartTrackballInfo( - position: points[nearestPointIndex], - point: chartPoint, - series: series, - pointIndex: pointIndex, - seriesIndex: series.index, - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; } - return null; - } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); } /// Gets the color of the series. @@ -1119,6 +1038,7 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer @override void computeNonEmptyYValues() { + nonEmptyYValues.clear(); if (emptyPointSettings.mode == EmptyPointMode.drop) { final List yValuesCopy = [...yValues]; nonEmptyYValues = yValuesCopy; @@ -1130,7 +1050,8 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer } else if (emptyPointSettings.mode == EmptyPointMode.gap) { super.computeNonEmptyYValues(); } else { - nonEmptyYValues = yValues; + final List yValuesCopy = [...yValues]; + nonEmptyYValues = yValuesCopy; } } @@ -1203,59 +1124,6 @@ class SplineAreaSeriesRenderer extends XyDataSeriesRenderer borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is SplineAreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { final Rect clip = clipRect(paintBounds, animationFactor, @@ -1293,6 +1161,7 @@ class SplineAreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _startControlHighPoints = []; @@ -1308,6 +1177,7 @@ class SplineAreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); _oldStartControlHighPoints.clear(); @@ -1396,6 +1266,7 @@ class SplineAreaSegment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _startControlHighPoints.clear(); @@ -1408,38 +1279,8 @@ class SplineAreaSegment extends ChartSegment { } _calculatePoints(_xValues, _yValues); - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - final List lerpStartControlHighPoints = - _lerpPoints(_oldStartControlHighPoints, _startControlHighPoints); - final List lerpEndControlHighPoints = - _lerpPoints(_oldEndControlHighPoints, _endControlHighPoints); - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints, - lerpStartControlHighPoints, lerpEndControlHighPoints); - - switch (series.borderDrawMode) { - case BorderDrawMode.all: - _strokePath = _fillPath; - break; - case BorderDrawMode.top: - _createTopStrokePath( - _strokePath, - lerpHighPoints, - lerpStartControlHighPoints, - lerpEndControlHighPoints, - ); - break; - case BorderDrawMode.excludeBottom: - _createExcludeBottomStrokePath( - _strokePath, - lerpHighPoints, - lerpLowPoints, - lerpStartControlHighPoints, - lerpEndControlHighPoints, - ); - break; - } + _createFillPath(_fillPath, _highPoints, _lowPoints, _startControlHighPoints, + _endControlHighPoints); } void _calculatePoints(List xValues, List yValues) { @@ -1456,6 +1297,7 @@ class SplineAreaSegment extends ChartSegment { continue; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); _highPoints.add(highPoint); points.add(highPoint); @@ -1494,6 +1336,48 @@ class SplineAreaSegment extends ChartSegment { } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + final List lerpStartControlHighPoints = + _lerpPoints(_oldStartControlHighPoints, _startControlHighPoints); + final List lerpEndControlHighPoints = + _lerpPoints(_oldEndControlHighPoints, _endControlHighPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints, + lerpStartControlHighPoints, lerpEndControlHighPoints); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath( + _strokePath, + lerpHighPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + ); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + ); + break; + } + } + List _lerpPoints(List oldPoints, List newPoints) { final List lerpPoints = []; final int oldPointsLength = oldPoints.length; @@ -1723,7 +1607,13 @@ class SplineAreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; final num y = chartPoint.y!; final double dx = series.pointToPixelX(x, y); @@ -1739,13 +1629,13 @@ class SplineAreaSegment extends ChartSegment { series.localToGlobal(preferredPos.translate(0, markerHeight)), text: series.tooltipText(chartPoint), header: series.name, - data: series.dataSource![currentSegmentIndex], + data: series.dataSource![pointIndex], point: chartPoint, series: series.widget, renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: currentSegmentIndex, + pointIndex: actualPointIndex, markerColors: [fillPaint.color], markerType: marker.type, ); @@ -1754,64 +1644,30 @@ class SplineAreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = points[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { if ((points[i] - position).distance <= pointDistance) { @@ -1836,6 +1692,8 @@ class SplineAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -1858,6 +1716,7 @@ class SplineAreaSegment extends ChartSegment { _strokePath.reset(); points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _startControlHighPoints.clear(); @@ -2146,59 +2005,6 @@ class SplineRangeAreaSeriesRenderer extends RangeSeriesRendererBase borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is SplineRangeAreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.highValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -2244,6 +2050,7 @@ class SplineRangeAreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _startControlHighPoints = []; @@ -2263,6 +2070,7 @@ class SplineRangeAreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); _oldStartControlHighPoints.clear(); @@ -2403,6 +2211,7 @@ class SplineRangeAreaSegment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _startControlHighPoints.clear(); @@ -2417,43 +2226,15 @@ class SplineRangeAreaSegment extends ChartSegment { } _calculatePoints(); - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - final List lerpStartControlHighPoints = - _lerpPoints(_oldStartControlHighPoints, _startControlHighPoints); - final List lerpEndControlHighPoints = - _lerpPoints(_oldEndControlHighPoints, _endControlHighPoints); - final List lerpStartControlLowPoints = - _lerpPoints(_oldStartControlLowPoints, _startControlLowPoints); - final List lerpEndControlLowPoints = - _lerpPoints(_oldEndControlLowPoints, _endControlLowPoints); _createFillPath( _fillPath, - lerpHighPoints, - lerpLowPoints, - lerpStartControlHighPoints, - lerpEndControlHighPoints, - lerpStartControlLowPoints, - lerpEndControlLowPoints, + _highPoints, + _lowPoints, + _startControlHighPoints, + _endControlHighPoints, + _startControlLowPoints, + _endControlLowPoints, ); - - switch (series.borderDrawMode) { - case RangeAreaBorderMode.all: - _strokePath = _fillPath; - break; - case RangeAreaBorderMode.excludeSides: - _createStrokePathForExcludeSides( - _strokePath, - lerpHighPoints, - lerpLowPoints, - lerpStartControlHighPoints, - lerpEndControlHighPoints, - lerpStartControlLowPoints, - lerpEndControlLowPoints, - ); - break; - } } void _calculatePoints() { @@ -2474,6 +2255,7 @@ class SplineRangeAreaSegment extends ChartSegment { highY = lowY = double.nan; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, highY), transformY(x, highY)); _highPoints.add(highPoint); @@ -2523,6 +2305,53 @@ class SplineRangeAreaSegment extends ChartSegment { } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty || _lowPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + final List lerpStartControlHighPoints = + _lerpPoints(_oldStartControlHighPoints, _startControlHighPoints); + final List lerpEndControlHighPoints = + _lerpPoints(_oldEndControlHighPoints, _endControlHighPoints); + final List lerpStartControlLowPoints = + _lerpPoints(_oldStartControlLowPoints, _startControlLowPoints); + final List lerpEndControlLowPoints = + _lerpPoints(_oldEndControlLowPoints, _endControlLowPoints); + _createFillPath( + _fillPath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + lerpStartControlLowPoints, + lerpEndControlLowPoints, + ); + + switch (series.borderDrawMode) { + case RangeAreaBorderMode.all: + _strokePath = _fillPath; + break; + case RangeAreaBorderMode.excludeSides: + _createStrokePathForExcludeSides( + _strokePath, + lerpHighPoints, + lerpLowPoints, + lerpStartControlHighPoints, + lerpEndControlHighPoints, + lerpStartControlLowPoints, + lerpEndControlLowPoints, + ); + break; + } + } + List _lerpPoints(List oldPoints, List newPoints) { final List lerpPoints = []; final int oldPointsLength = oldPoints.length; @@ -2730,7 +2559,13 @@ class SplineRangeAreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; final num high = chartPoint.high!; final double dx = series.pointToPixelX(x, high); @@ -2754,7 +2589,7 @@ class SplineRangeAreaSegment extends ChartSegment { renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: pointIndex, + pointIndex: actualPointIndex, hasMultipleYValues: true, markerColors: [fillPaint.color], markerType: marker.type, @@ -2764,70 +2599,33 @@ class SplineRangeAreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); - final num x = _xValues[nearestPointIndex]; - final num high = _highValues[nearestPointIndex]; - final num low = _lowValues[nearestPointIndex]; + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && _highPoints.isNotEmpty) { + final Offset preferredPos = _highPoints[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, + highXPos: preferredPos.dx, + highYPos: preferredPos.dy, + lowYPos: series.pointToPixelY(chartPoint.xValue!, chartPoint.low!), point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, - lowYPos: series.pointToPixelY(x, low), - highYPos: series.pointToPixelY(x, high), - highXPos: series.pointToPixelX(x, high), + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { final Offset a = points[i]; @@ -2857,6 +2655,8 @@ class SplineRangeAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -2882,6 +2682,7 @@ class SplineRangeAreaSegment extends ChartSegment { _strokePath.reset(); points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _startControlHighPoints.clear(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart index 6dc928e65..4a173220b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area100_series.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -131,7 +131,7 @@ class StackedArea100Series extends StackedSeriesBase { /// Creates series renderer for 100% stacked area series. class StackedArea100SeriesRenderer extends StackedSeriesRenderer - with ContinuousSeriesMixin { + with ContinuousSeriesMixin, Stacking100SeriesMixin { BorderDrawMode get borderDrawMode => _borderDrawMode; BorderDrawMode _borderDrawMode = BorderDrawMode.top; set borderDrawMode(BorderDrawMode value) { @@ -183,59 +183,6 @@ class StackedArea100SeriesRenderer extends StackedSeriesRenderer borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StackedArea100Segment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -261,6 +208,8 @@ class StackedArea100Segment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _oldHighPoints = []; @@ -271,6 +220,7 @@ class StackedArea100Segment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); return; @@ -320,6 +270,7 @@ class StackedArea100Segment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); if (_xValues.isEmpty || _topValues.isEmpty || _bottomValues.isEmpty) { @@ -340,48 +291,7 @@ class StackedArea100Segment extends ChartSegment { _calculateDropPoints(_xValues, _topValues, _bottomValues); break; } - - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - - switch (series.emptyPointSettings.mode) { - case EmptyPointMode.gap: - case EmptyPointMode.zero: - case EmptyPointMode.average: - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - break; - - case EmptyPointMode.drop: - _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - break; - } - - switch (series.borderDrawMode) { - case BorderDrawMode.all: - _strokePath = _fillPath; - break; - - case BorderDrawMode.top: - _createTopStrokePath(_strokePath, lerpHighPoints); - break; - - case BorderDrawMode.excludeBottom: - switch (series.emptyPointSettings.mode) { - case EmptyPointMode.gap: - case EmptyPointMode.zero: - case EmptyPointMode.average: - _createExcludeBottomStrokePath( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - - case EmptyPointMode.drop: - _createExcludeBottomStrokePathForDrop( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - } - break; - } + _createFillPath(_fillPath, _highPoints, _lowPoints); } void _calculatePoints( @@ -402,6 +312,7 @@ class StackedArea100Segment extends ChartSegment { topY = bottomY = double.nan; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); _highPoints.add(highPoint); @@ -436,6 +347,7 @@ class StackedArea100Segment extends ChartSegment { continue; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); _highPoints.add(highPoint); points.add(highPoint); @@ -463,6 +375,57 @@ class StackedArea100Segment extends ChartSegment { } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + } + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + + case BorderDrawMode.excludeBottom: + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createExcludeBottomStrokePath( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createExcludeBottomStrokePathForDrop( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + } + break; + } + } + List _lerpPoints(List oldPoints, List newPoints) { final List lerpPoints = []; final int oldPointsLength = oldPoints.length; @@ -644,9 +607,7 @@ class StackedArea100Segment extends ChartSegment { final Offset lowPoint = lowPoints[lastIndex]; path!.lineTo(lowPoint.dx, lowPoint.dy); - if (path != null) { - source.addPath(path, Offset.zero); - } + source.addPath(path, Offset.zero); return source; } @@ -675,9 +636,15 @@ class StackedArea100Segment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; - final num y = series.topValues[pointIndex]; + final num y = series.topValues[actualPointIndex]; final double dx = series.pointToPixelX(x, y); final double dy = series.pointToPixelY(x, y); final ChartMarker marker = series.markerAt(pointIndex); @@ -699,7 +666,7 @@ class StackedArea100Segment extends ChartSegment { renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: pointIndex, + pointIndex: actualPointIndex, markerColors: [fillPaint.color], markerType: marker.type, ); @@ -708,64 +675,30 @@ class StackedArea100Segment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = points[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { if ((points[i] - position).distance <= pointDistance) { @@ -790,6 +723,8 @@ class StackedArea100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -804,6 +739,7 @@ class StackedArea100Segment extends ChartSegment { @override void dispose() { _fillPath.reset(); + _drawIndexes.clear(); _strokePath.reset(); super.dispose(); } diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart index 9e6eeab87..daaa7c76f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_area_series.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -182,59 +182,6 @@ class StackedAreaSeriesRenderer extends StackedSeriesRenderer borderGradient: borderGradient); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StackedAreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -260,6 +207,8 @@ class StackedAreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _oldHighPoints = []; @@ -270,6 +219,7 @@ class StackedAreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); return; @@ -319,6 +269,7 @@ class StackedAreaSegment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); if (_xValues.isEmpty || _topValues.isEmpty || _bottomValues.isEmpty) { @@ -339,48 +290,7 @@ class StackedAreaSegment extends ChartSegment { _calculateDropPoints(_xValues, _topValues, _bottomValues); break; } - - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - - switch (series.emptyPointSettings.mode) { - case EmptyPointMode.gap: - case EmptyPointMode.zero: - case EmptyPointMode.average: - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - break; - - case EmptyPointMode.drop: - _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - break; - } - - switch (series.borderDrawMode) { - case BorderDrawMode.all: - _strokePath = _fillPath; - break; - - case BorderDrawMode.top: - _createTopStrokePath(_strokePath, lerpHighPoints); - break; - - case BorderDrawMode.excludeBottom: - switch (series.emptyPointSettings.mode) { - case EmptyPointMode.gap: - case EmptyPointMode.zero: - case EmptyPointMode.average: - _createExcludeBottomStrokePath( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - - case EmptyPointMode.drop: - _createExcludeBottomStrokePathForDrop( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - } - break; - } + _createFillPath(_fillPath, _highPoints, _lowPoints); } void _calculatePoints( @@ -401,6 +311,7 @@ class StackedAreaSegment extends ChartSegment { topY = bottomY = double.nan; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); _highPoints.add(highPoint); @@ -435,6 +346,7 @@ class StackedAreaSegment extends ChartSegment { continue; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, topY), transformY(x, topY)); _highPoints.add(highPoint); points.add(highPoint); @@ -462,6 +374,57 @@ class StackedAreaSegment extends ChartSegment { } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createDropFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + break; + } + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + + case BorderDrawMode.excludeBottom: + switch (series.emptyPointSettings.mode) { + case EmptyPointMode.gap: + case EmptyPointMode.zero: + case EmptyPointMode.average: + _createExcludeBottomStrokePath( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + + case EmptyPointMode.drop: + _createExcludeBottomStrokePathForDrop( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + } + break; + } + } + List _lerpPoints(List oldPoints, List newPoints) { final List lerpPoints = []; final int oldPointsLength = oldPoints.length; @@ -643,9 +606,7 @@ class StackedAreaSegment extends ChartSegment { final Offset lowPoint = lowPoints[lastIndex]; path!.lineTo(lowPoint.dx, lowPoint.dy); - if (path != null) { - source.addPath(path, Offset.zero); - } + source.addPath(path, Offset.zero); return source; } @@ -674,9 +635,15 @@ class StackedAreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; - final num y = series.topValues[pointIndex]; + final num y = series.topValues[actualPointIndex]; final double dx = series.pointToPixelX(x, y); final double dy = series.pointToPixelY(x, y); final ChartMarker marker = series.markerAt(pointIndex); @@ -698,7 +665,7 @@ class StackedAreaSegment extends ChartSegment { renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: pointIndex, + pointIndex: actualPointIndex, markerColors: [fillPaint.color], markerType: marker.type, ); @@ -707,64 +674,30 @@ class StackedAreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = points[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { if ((points[i] - position).distance <= pointDistance) { @@ -789,6 +722,8 @@ class StackedAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -804,6 +739,7 @@ class StackedAreaSegment extends ChartSegment { void dispose() { _fillPath.reset(); _strokePath.reset(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); _oldHighPoints.clear(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart index 5efeaf4d5..0c4d60b50 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar100_series.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -148,7 +148,11 @@ class StackedBar100Series extends StackedSeriesBase { /// Creates series renderer for 100% stacked bar series. class StackedBar100SeriesRenderer extends StackedSeriesRenderer - with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + with + SbsSeriesMixin, + ClusterSeriesMixin, + SegmentAnimationMixin, + Stacking100SeriesMixin { Color get borderColor => _borderColor; Color _borderColor = Colors.transparent; set borderColor(Color value) { @@ -253,48 +257,6 @@ class StackedBar100SeriesRenderer extends StackedSeriesRenderer gradient: gradient, borderGradient: borderGradient); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is StackedBar100Segment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for 100% stacked bar series. @@ -409,21 +371,20 @@ class StackedBar100Segment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart index 62c85b58d..511a7ed59 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_bar_series.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -268,48 +268,6 @@ class StackedBarSeriesRenderer extends StackedSeriesRenderer @override ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.stackedBarSeries; - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is StackedBarSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for stacked bar series. @@ -429,21 +387,20 @@ class StackedBarSegment extends ChartSegment with BarSeriesTrackerMixin { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; @@ -464,8 +421,10 @@ class StackedBarSegment extends ChartSegment with BarSeriesTrackerMixin { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - // Draws the tracker bounds. - super.onPaint(canvas); + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } if (segmentRect == null) { return; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart index c43e2e48b..e3396b6c1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column100_series.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -146,7 +146,11 @@ class StackedColumn100Series extends StackedSeriesBase { /// Creates series renderer for 100% stacked column series. class StackedColumn100SeriesRenderer extends StackedSeriesRenderer - with SbsSeriesMixin, ClusterSeriesMixin, SegmentAnimationMixin { + with + SbsSeriesMixin, + ClusterSeriesMixin, + SegmentAnimationMixin, + Stacking100SeriesMixin { Color get borderColor => _borderColor; Color _borderColor = Colors.transparent; set borderColor(Color value) { @@ -252,48 +256,6 @@ class StackedColumn100SeriesRenderer extends StackedSeriesRenderer gradient: gradient, borderGradient: borderGradient); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is StackedColumn100Segment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for 100% stacked column series. @@ -408,21 +370,20 @@ class StackedColumn100Segment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart index 71e68ffc2..05b3a83da 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_column_series.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -268,48 +268,6 @@ class StackedColumnSeriesRenderer extends StackedSeriesRenderer gradient: gradient, borderGradient: borderGradient); } - - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is StackedColumnSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } } /// Segment class for stacked column series. @@ -430,21 +388,20 @@ class StackedColumnSegment extends ChartSegment } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; @@ -465,8 +422,10 @@ class StackedColumnSegment extends ChartSegment /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - // Draws the tracker bounds. - super.onPaint(canvas); + if (series.isTrackVisible) { + // Draws the tracker bounds. + super.onPaint(canvas); + } if (segmentRect == null) { return; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart index 87d7e0410..0ddd26ed9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line100_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -99,7 +99,7 @@ class StackedLine100Series extends StackedSeriesBase { /// Creates series renderer for 100% stacked line series. class StackedLine100SeriesRenderer extends StackedSeriesRenderer - with LineSeriesMixin { + with LineSeriesMixin, Stacking100SeriesMixin { @override double legendIconBorderWidth() { return 3; @@ -146,47 +146,6 @@ class StackedLine100SeriesRenderer extends StackedSeriesRenderer updateSegmentGradient(segment); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StackedLine100Segment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -333,48 +292,25 @@ class StackedLine100Segment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final int segmentIndex = nearestPointIndex == 0 - ? currentSegmentIndex - : currentSegmentIndex + 1; - final int pointIndex = clampInt(segmentIndex, 0, series.dataCount - 1); - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); - return ChartTrackballInfo( - position: points[nearestPointIndex], - point: chartPoint, - series: series, - pointIndex: pointIndex, - seriesIndex: series.index, - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; } - return null; - } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= points[0].dx; - nearPointY ??= series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final double curX = points[i].dx; - final double curY = points[i].dy; - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - return pointIndex ?? -1; + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); } /// Gets the color of the series. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart index 2955a300b..46dafde7d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stacked_line_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -147,47 +147,6 @@ class StackedLineSeriesRenderer extends StackedSeriesRenderer updateSegmentGradient(segment); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StackedLineSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -335,48 +294,25 @@ class StackedLineSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final int segmentIndex = nearestPointIndex == 0 - ? currentSegmentIndex - : currentSegmentIndex + 1; - final int pointIndex = clampInt(segmentIndex, 0, series.dataCount - 1); - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); - return ChartTrackballInfo( - position: points[nearestPointIndex], - point: chartPoint, - series: series, - pointIndex: pointIndex, - seriesIndex: series.index, - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; } - return null; - } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= points[0].dx; - nearPointY ??= series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final double curX = points[i].dx; - final double curY = points[i].dy; - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - return pointIndex ?? -1; + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); } /// Gets the color of the series. diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart index e0797286e..95680f99d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/step_area_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -178,59 +178,6 @@ class StepAreaSeriesRenderer extends XyDataSeriesRenderer @override ShapeMarkerType effectiveLegendIconType() => ShapeMarkerType.stepAreaSeries; - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StepAreaSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - - @override - void onRealTimeAnimationUpdate() { - super.onRealTimeAnimationUpdate(); - if (segments.isNotEmpty) { - final ChartSegment segment = segments[0]; - segment.animationFactor = segmentAnimationFactor; - segment.transformValues(); - customizeSegment(segment); - } - markNeedsPaint(); - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -255,6 +202,7 @@ class StepAreaSegment extends ChartSegment { final Path _fillPath = Path(); Path _strokePath = Path(); + final List _drawIndexes = []; final List _highPoints = []; final List _lowPoints = []; final List _oldHighPoints = []; @@ -265,6 +213,7 @@ class StepAreaSegment extends ChartSegment { double seriesAnimationFactor, double segmentAnimationFactor) { if (series.animationType == AnimationType.loading) { points.clear(); + _drawIndexes.clear(); _oldHighPoints.clear(); _oldLowPoints.clear(); return; @@ -314,6 +263,7 @@ class StepAreaSegment extends ChartSegment { @override void transformValues() { points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); @@ -324,23 +274,7 @@ class StepAreaSegment extends ChartSegment { } _calculatePoints(_xValues, _yValues); - final List lerpHighPoints = - _lerpPoints(_oldHighPoints, _highPoints); - final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); - _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); - - switch (series.borderDrawMode) { - case BorderDrawMode.all: - _strokePath = _fillPath; - break; - case BorderDrawMode.top: - _createTopStrokePath(_strokePath, lerpHighPoints); - break; - case BorderDrawMode.excludeBottom: - _createExcludeBottomStrokePath( - _strokePath, lerpHighPoints, lerpLowPoints); - break; - } + _createFillPath(_fillPath, _highPoints, _lowPoints); } void _calculatePoints(List xValues, List yValues) { @@ -356,6 +290,7 @@ class StepAreaSegment extends ChartSegment { continue; } + _drawIndexes.add(i); final Offset highPoint = Offset(transformX(x, high), transformY(x, high)); _highPoints.add(highPoint); @@ -373,6 +308,32 @@ class StepAreaSegment extends ChartSegment { } } + void _computeAreaPath() { + _fillPath.reset(); + _strokePath.reset(); + + if (_highPoints.isEmpty) { + return; + } + final List lerpHighPoints = + _lerpPoints(_oldHighPoints, _highPoints); + final List lerpLowPoints = _lerpPoints(_oldLowPoints, _lowPoints); + _createFillPath(_fillPath, lerpHighPoints, lerpLowPoints); + + switch (series.borderDrawMode) { + case BorderDrawMode.all: + _strokePath = _fillPath; + break; + case BorderDrawMode.top: + _createTopStrokePath(_strokePath, lerpHighPoints); + break; + case BorderDrawMode.excludeBottom: + _createExcludeBottomStrokePath( + _strokePath, lerpHighPoints, lerpLowPoints); + break; + } + } + List _lerpPoints(List oldPoints, List newPoints) { final List lerpPoints = []; final int oldPointsLength = oldPoints.length; @@ -534,7 +495,13 @@ class StepAreaSegment extends ChartSegment { TooltipInfo? tooltipInfo({Offset? position, int? pointIndex}) { pointIndex ??= _findNearestChartPointIndex(points, position!); if (pointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + final Offset position = points[pointIndex]; + if (position.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); final num x = chartPoint.xValue!; final num y = chartPoint.y!; final double dx = series.pointToPixelX(x, y); @@ -558,7 +525,7 @@ class StepAreaSegment extends ChartSegment { renderer: series, seriesIndex: series.index, segmentIndex: currentSegmentIndex, - pointIndex: pointIndex, + pointIndex: actualPointIndex, markerColors: [fillPaint.color], markerType: marker.type, ); @@ -567,64 +534,30 @@ class StepAreaSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final int nearestPointIndex = _findNearestPoint(points, position); - if (nearestPointIndex != -1) { - final CartesianChartPoint chartPoint = _chartPoint(nearestPointIndex); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && points.isNotEmpty) { + final Offset preferredPos = points[pointIndex]; + if (preferredPos.isNaN) { + return null; + } + + final int actualPointIndex = _drawIndexes[pointIndex]; + final CartesianChartPoint chartPoint = _chartPoint(actualPointIndex); return ChartTrackballInfo( - position: points[nearestPointIndex], + position: preferredPos, point: chartPoint, series: series, - pointIndex: nearestPointIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: actualPointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; } - int _findNearestPoint(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= series.isTransposed - ? series.xAxis!.visibleRange!.minimum - : points[0].dx; - nearPointY ??= series.isTransposed - ? points[0].dy - : series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final num touchYValue = position.dy; - final double curX = points[i].dx; - final double curY = points[i].dy; - - if (series.isTransposed) { - if (delta == touchYValue - curY) { - pointIndex = i; - } else if ((touchYValue - curY).abs() <= - (touchYValue - nearPointY).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchYValue - curY; - pointIndex = i; - } - } else { - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - } - return pointIndex ?? -1; - } - int _findNearestChartPointIndex(List points, Offset position) { for (int i = 0; i < points.length; i++) { if ((points[i] - position).distance <= pointDistance) { @@ -649,6 +582,8 @@ class StepAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _computeAreaPath(); + Paint paint = getFillPaint(); if (paint.color != Colors.transparent) { canvas.drawPath(_fillPath, paint); @@ -666,6 +601,7 @@ class StepAreaSegment extends ChartSegment { _strokePath.reset(); points.clear(); + _drawIndexes.clear(); _highPoints.clear(); _lowPoints.clear(); super.dispose(); diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart index 604c2bc35..ebbd46f8a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/stepline_series.dart @@ -3,11 +3,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/core_tooltip.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/helper.dart'; import '../utils/typedef.dart'; @@ -126,47 +126,6 @@ class StepLineSeriesRenderer extends XyDataSeriesRenderer updateSegmentGradient(segment); } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - for (final ChartSegment segment in segments) { - if (segment is StepLineSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override void onPaint(PaintingContext context, Offset offset) { context.canvas.save(); @@ -327,49 +286,25 @@ class StepLineSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - final List linePoints = [points.first, points.last]; - final int nearestPointIndex = _findNearestPoints(linePoints, position); - if (nearestPointIndex != -1) { - final int segmentIndex = nearestPointIndex == 0 - ? currentSegmentIndex - : currentSegmentIndex + 1; - final int pointIndex = clampInt(segmentIndex, 0, series.dataCount - 1); - final CartesianChartPoint chartPoint = _chartPoint(pointIndex); - return ChartTrackballInfo( - position: linePoints[nearestPointIndex], - point: chartPoint, - series: series, - pointIndex: currentSegmentIndex, - seriesIndex: series.index, - ); + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + final CartesianChartPoint chartPoint = _chartPoint(pointIndex); + if (pointIndex == -1 || + points.isEmpty || + (chartPoint.y != null && chartPoint.y!.isNaN)) { + return null; } - return null; - } - int _findNearestPoints(List points, Offset position) { - double delta = 0; - num? nearPointX; - num? nearPointY; - int? pointIndex; - for (int i = 0; i < points.length; i++) { - nearPointX ??= points[0].dx; - nearPointY ??= series.yAxis!.visibleRange!.minimum; - - final num touchXValue = position.dx; - final double curX = points[i].dx; - final double curY = points[i].dy; - if (delta == touchXValue - curX) { - pointIndex = i; - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - pointIndex = i; - } - } - return pointIndex ?? -1; + return ChartTrackballInfo( + position: points[0], + point: chartPoint, + series: series, + seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, + ); } @override diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart b/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart index e972ba4eb..1b0616c6c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/series/waterfall_series.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; import '../base.dart'; +import '../behaviors/trackball.dart'; import '../common/chart_point.dart'; import '../common/connector_line.dart'; import '../common/core_tooltip.dart'; import '../common/element_widget.dart'; import '../common/marker.dart'; import '../interactions/tooltip.dart'; -import '../interactions/trackball.dart'; import '../utils/constants.dart'; import '../utils/enum.dart'; import '../utils/helper.dart'; @@ -477,9 +477,11 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { - super.populateDataSource(yPaths, chaoticYLists, yLists, fPaths, fLists); + super.populateDataSource( + yPaths, chaoticYLists, yLists, fPaths, chaoticFLists, fLists); _calculateWaterfallValues(); populateChartPoints(); } @@ -509,6 +511,7 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer List>? chaoticYLists, List>? yLists, List>? fPaths, + List>? chaoticFLists, List>? fLists, ]) { super.updateDataPoints( @@ -519,17 +522,23 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer chaoticYLists, yLists, fPaths, + chaoticFLists, fLists, ); _calculateWaterfallValues(); } - void _calculateWaterfallValues() { + void _resetDataSourceHolders() { _intermediateSumValues.clear(); _totalSumValues.clear(); _highValues.clear(); _lowValues.clear(); _waterfallYValues.clear(); + } + + void _calculateWaterfallValues() { + _resetDataSourceHolders(); + num topValue = 0; num bottomValue = 0; num intermediateOrigin = 0; @@ -573,6 +582,9 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer yMax = maxY.isNaN ? yMax : maxY; } + @override + num trackballYValue(int index) => _highValues[index]; + @override void setData(int index, ChartSegment segment) { super.setData(index, segment); @@ -606,6 +618,7 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer ..xValues = xValues ..yLists = >[_highValues] ..stackedYValues = _waterfallYValues + ..sortedIndexes = sortedIndexes ..animation = dataLabelAnimation ..layout(constraints); } @@ -641,53 +654,11 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer borderGradient: borderGradient); segment.connectorLineStrokePaint - ..color = connectorLineSettings.color ?? - chartThemeData!.waterfallConnectorLineColor + ..color = (connectorLineSettings.color ?? + chartThemeData!.waterfallConnectorLineColor)! ..strokeWidth = connectorLineSettings.width; } - @override - List contains(Offset position) { - if (animationController != null && animationController!.isAnimating) { - return []; - } - final List segmentCollection = []; - int index = 0; - double delta = 0; - num? nearPointX; - num? nearPointY; - - for (final ChartSegment segment in segments) { - if (segment is WaterfallSegment) { - nearPointX ??= segment.series.xValues[0]; - nearPointY ??= segment.series.yAxis!.visibleRange!.minimum; - final Rect rect = segment.series.paintBounds; - - final num touchXValue = - segment.series.xAxis!.pixelToPoint(rect, position.dx, position.dy); - final num touchYValue = - segment.series.yAxis!.pixelToPoint(rect, position.dx, position.dy); - final double curX = segment.series.xValues[index].toDouble(); - final double curY = segment.series.yValues[index].toDouble(); - if (delta == touchXValue - curX) { - if ((touchYValue - curY).abs() > (touchYValue - nearPointY).abs()) { - segmentCollection.clear(); - } - segmentCollection.add(segment); - } else if ((touchXValue - curX).abs() <= - (touchXValue - nearPointX).abs()) { - nearPointX = curX; - nearPointY = curY; - delta = touchXValue - curX; - segmentCollection.clear(); - segmentCollection.add(segment); - } - } - index++; - } - return segmentCollection; - } - @override Offset dataLabelPosition(ChartElementParentData current, ChartDataLabelAlignment alignment, Size size) { @@ -766,9 +737,7 @@ class WaterfallSeriesRenderer extends XyDataSeriesRenderer @override void dispose() { - _highValues.clear(); - _lowValues.clear(); - _waterfallYValues.clear(); + _resetDataSourceHolders(); super.dispose(); } } @@ -916,21 +885,20 @@ class WaterfallSegment extends ChartSegment { } @override - TrackballInfo? trackballInfo(Offset position) { - if (segmentRect != null) { + TrackballInfo? trackballInfo(Offset position, int pointIndex) { + if (pointIndex != -1 && segmentRect != null) { final CartesianChartPoint chartPoint = _chartPoint(); return ChartTrackballInfo( - position: series.isTransposed - ? series.yAxis!.isInversed - ? segmentRect!.outerRect.centerLeft - : segmentRect!.outerRect.centerRight - : series.yAxis!.isInversed - ? segmentRect!.outerRect.bottomCenter - : segmentRect!.outerRect.topCenter, + position: + Offset(series.pointToPixelX(x, top), series.pointToPixelY(x, top)), point: chartPoint, series: series, - pointIndex: currentSegmentIndex, seriesIndex: series.index, + segmentIndex: currentSegmentIndex, + pointIndex: pointIndex, + text: series.trackballText(chartPoint, series.name), + header: series.tooltipHeaderText(chartPoint), + color: fillPaint.color, ); } return null; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart b/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart index bb6cc2a3e..2aeeb573d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/trendline/trendline.dart @@ -654,10 +654,14 @@ class RenderTrendlineStack extends RenderBox List? buildLegendItems( int seriesIndex, LegendItemProvider provider) { final List legendItems = []; - int trendlineIndex = 0; + const int trendlineIndex = 0; TrendlineRenderer? child = firstChild; while (child != null) { - legendItems.addAll(child.buildLegendItems(trendlineIndex++, provider)!); + final List? items = + child.buildLegendItems(trendlineIndex, provider); + if (items != null) { + legendItems.addAll(items); + } child = childAfter(child); } return legendItems; @@ -914,7 +918,9 @@ class TrendlineRenderer extends RenderBox { set isVisibleInLegend(bool value) { if (_isVisibleInLegend != value) { _isVisibleInLegend = value; - series!.markNeedsLegendUpdate(); + if (series != null) { + series!.markNeedsLegendUpdate(); + } } } @@ -2065,7 +2071,7 @@ class TrendlineRenderer extends RenderBox { late Function(num, [bool]) polynomialForeCastValue; late List sortedXValues; - if (series!.hasLinearData) { + if (series!.canFindLinearVisibleIndexes) { sortedXValues = seriesXValues; } else { final List xValuesCopy = [...seriesXValues]; @@ -2222,7 +2228,9 @@ class TrendlineRenderer extends RenderBox { void _updateLegendBasedOnSeries(bool seriesToggled) { if (!isToggled) { - _legendItem!.onToggled?.call(); + if (_legendItem != null) { + _legendItem!.onToggled?.call(); + } } } @@ -2349,10 +2357,7 @@ class TrendlineRenderer extends RenderBox { } void _calculateMarkerPositions() { - final Color themeFillColor = - series!.parent!.chartThemeData!.brightness == Brightness.light - ? Colors.white - : Colors.black; + final Color themeFillColor = series!.parent!.themeData!.colorScheme.surface; final MarkerSettings settings = markerSettings; final int length = trendlineXValues.length; for (int i = 0; i < length; i++) { diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart index c8f8e68c1..5fb1d2abb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/constants.dart @@ -48,3 +48,24 @@ const EdgeInsetsDirectional tooltipItemSpacing = const EdgeInsets tooltipInnerPadding = EdgeInsets.all(6.0); const defaultLegendSizeFactor = 0.3; + +// Crosshair tooltip padding. +const double crosshairPadding = 10; + +// Indicator upper line text. +const String trackballUpperLineText = 'UpperLine'; + +// Indicator ceter line text. +const String trackballCenterText = 'CenterLine'; + +// Indicator lower line text. +const String trackballLowerLineText = 'LowerLine'; + +// Indicator period line text. +const String trackballPeriodLineText = 'PeriodLine'; + +// Indicator MACD line text. +const String trackballMACDLineText = 'MacdLine'; + +// Indicator histogram text. +const String trackballHistogramText = 'Histogram'; diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart index 561548260..ded5b1b8c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/helper.dart @@ -14,6 +14,7 @@ import '../axis/datetime_axis.dart'; import '../axis/datetime_category_axis.dart'; import '../axis/logarithmic_axis.dart'; import '../axis/numeric_axis.dart'; +import '../base.dart'; import '../common/callbacks.dart'; import '../common/chart_point.dart'; import '../common/core_legend.dart' as core; @@ -22,8 +23,11 @@ import '../common/layout_handler.dart'; import '../common/legend.dart'; import '../common/marker.dart'; import '../common/title.dart'; +import '../indicators/technical_indicator.dart'; import '../interactions/tooltip.dart'; +import '../series/bar_series.dart'; import '../series/chart_series.dart'; +import '../series/column_series.dart'; import '../utils/enum.dart'; import 'constants.dart'; @@ -297,9 +301,9 @@ Color dataLabelSurfaceColor( case ChartDataLabelPosition.outside: if (labelColor == Colors.transparent) { if (chartThemeData.plotAreaBackgroundColor != Colors.transparent) { - return chartThemeData.plotAreaBackgroundColor; + return chartThemeData.plotAreaBackgroundColor!; } else if (chartThemeData.backgroundColor != Colors.transparent) { - return chartThemeData.backgroundColor; + return chartThemeData.backgroundColor!; } return themeData.colorScheme.surface; } @@ -939,24 +943,581 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { replacingText, formatNumericValue(value, yAxis, digits)); } + String _formatTrackballLabel(num value, int digits, String text, bool isLtr) { + if (text.isEmpty) { + return formatNumericValue(value, yAxis, digits); + } + return isLtr + ? '$text: ${formatNumericValue(value, yAxis, digits)}' + : '${formatNumericValue(value, yAxis, digits)} :$text'; + } + String tooltipText(CartesianChartPoint point, [int outlierIndex = -1]) { if (xAxis == null || point.x == null) { return ''; } - String text = ''; - if (parent == null || parent!.tooltipBehavior == null) { + String text = ''; + if (parent == null || parent!.tooltipBehavior == null) { + return ''; + } + + final int digits = parent!.tooltipBehavior!.decimalPlaces; + if (!parent!.tooltipBehavior!.shared) { + text = tooltipHeaderText(point, digits); + } + + final bool isLtr = textDirection == TextDirection.ltr; + final String? tooltipFormat = parent?.tooltipBehavior?.format; + if (tooltipFormat != null) { + String tooltipText = tooltipFormat.replaceAll('point.x', text); + + if (point.y != null) { + tooltipText = _replace(tooltipText, 'point.y', point.y!, digits); + } + + if (point.high != null) { + tooltipText = _replace(tooltipText, 'point.high', point.high!, digits); + } + + if (point.low != null) { + tooltipText = _replace(tooltipText, 'point.low', point.low!, digits); + } + + if (point.open != null) { + tooltipText = _replace(tooltipText, 'point.open', point.open!, digits); + } + + if (point.close != null) { + tooltipText = + _replace(tooltipText, 'point.close', point.close!, digits); + } + + if (outlierIndex != -1) { + if (point.outliers != null && point.outliers!.isNotEmpty) { + tooltipText = _replace(tooltipText, 'point.outliers', + point.outliers![outlierIndex], digits); + } + } else { + if (point.minimum != null) { + tooltipText = + _replace(tooltipText, 'point.minimum', point.minimum!, digits); + } + + if (point.maximum != null) { + tooltipText = + _replace(tooltipText, 'point.maximum', point.maximum!, digits); + } + + if (point.lowerQuartile != null) { + tooltipText = _replace( + tooltipText, 'point.lowerQuartile', point.lowerQuartile!, digits); + } + + if (point.upperQuartile != null) { + tooltipText = _replace( + tooltipText, 'point.upperQuartile', point.upperQuartile!, digits); + } + + if (point.mean != null) { + tooltipText = + _replace(tooltipText, 'point.mean', point.mean!, digits); + } + + if (point.median != null) { + tooltipText = + _replace(tooltipText, 'point.median', point.median!, digits); + } + } + + if (point.cumulative != null) { + tooltipText = _replace( + tooltipText, 'point.cumulative', point.cumulative!, digits); + } + + if (point.bubbleSize != null) { + tooltipText = + _replace(tooltipText, 'point.size', point.bubbleSize!, digits); + } + + tooltipText = tooltipText.replaceAll('series.name', name); + text = isLtr ? tooltipText : formatRTLText(tooltipText); + } else { + if (point.y != null) { + text = _formatTooltipLabel(point.y!, digits, text, isLtr); + } + + if (point.high != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.high!, digits, 'High', isLtr); + } + + if (point.low != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.low!, digits, 'Low', isLtr); + } + + if (point.open != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.open!, digits, 'Open', isLtr); + } + + if (point.close != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTooltipLabel(point.close!, digits, 'Close', isLtr); + } + + if (outlierIndex != -1) { + if (point.outliers != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel( + point.outliers![outlierIndex], digits, 'Outliers', isLtr); + } + } else { + if (point.minimum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.minimum!, digits, 'Minimum', isLtr); + } + + if (point.maximum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.maximum!, digits, 'Maximum', isLtr); + } + + if (point.median != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.median!, digits, 'Median', isLtr); + } + + if (point.mean != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTooltipLabel(point.mean!, digits, 'Mean', isLtr); + } + + if (point.lowerQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += + _formatTooltipLabel(point.lowerQuartile!, digits, 'LQ', isLtr); + } + + if (point.upperQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += + _formatTooltipLabel(point.upperQuartile!, digits, 'HQ', isLtr); + } + } + } + + return text; + } + + String trackballText(CartesianChartPoint point, String seriesName, + {int outlierIndex = -1}) { + if (parent == null || + parent!.trackballBehavior == null || + xAxis == null || + point.x == null) { + return ''; + } + + String text = ''; + final int digits = parent!.trackballBehavior!.tooltipSettings.decimalPlaces; + if (parent!.trackballBehavior!.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + text = seriesName; + } + + final bool isLtr = textDirection == TextDirection.ltr; + final String? tooltipFormat = + parent!.trackballBehavior!.tooltipSettings.format; + if (tooltipFormat != null) { + text = tooltipHeaderText(point, digits); + String tooltipText = tooltipFormat.replaceAll('point.x', text); + + if (point.y != null) { + tooltipText = _replace(tooltipText, 'point.y', point.y!, digits); + } + + if (point.high != null) { + tooltipText = _replace(tooltipText, 'point.high', point.high!, digits); + } + + if (point.low != null) { + tooltipText = _replace(tooltipText, 'point.low', point.low!, digits); + } + + if (point.open != null) { + tooltipText = _replace(tooltipText, 'point.open', point.open!, digits); + } + + if (point.close != null) { + tooltipText = + _replace(tooltipText, 'point.close', point.close!, digits); + } + + if (outlierIndex != -1) { + if (point.outliers != null && point.outliers!.isNotEmpty) { + tooltipText = _replace(tooltipText, 'point.outliers', + point.outliers![outlierIndex], digits); + } + } else { + if (point.minimum != null) { + tooltipText = + _replace(tooltipText, 'point.minimum', point.minimum!, digits); + } + + if (point.maximum != null) { + tooltipText = + _replace(tooltipText, 'point.maximum', point.maximum!, digits); + } + + if (point.lowerQuartile != null) { + tooltipText = _replace( + tooltipText, 'point.lowerQuartile', point.lowerQuartile!, digits); + } + + if (point.upperQuartile != null) { + tooltipText = _replace( + tooltipText, 'point.upperQuartile', point.upperQuartile!, digits); + } + + if (point.mean != null) { + tooltipText = + _replace(tooltipText, 'point.mean', point.mean!, digits); + } + + if (point.median != null) { + tooltipText = + _replace(tooltipText, 'point.median', point.median!, digits); + } + } + + if (point.cumulative != null) { + tooltipText = _replace( + tooltipText, 'point.cumulative', point.cumulative!, digits); + } + + if (point.bubbleSize != null) { + tooltipText = + _replace(tooltipText, 'point.size', point.bubbleSize!, digits); + } + + tooltipText = tooltipText.replaceAll('series.name', seriesName); + text = isLtr ? tooltipText : formatRTLText(tooltipText); + } else { + if (point.y != null) { + text = _formatTrackballLabel(point.y!, digits, text, isLtr); + } + + if (point.high != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.high!, digits, 'High', isLtr); + } + + if (point.low != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.low!, digits, 'Low', isLtr); + } + + if (point.open != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.open!, digits, 'Open', isLtr); + } + + if (point.close != null) { + if (text.isNotEmpty) { + text += '\n'; + } + text += _formatTrackballLabel(point.close!, digits, 'Close', isLtr); + } + + if (outlierIndex != -1) { + if (point.outliers != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.outliers![outlierIndex], digits, 'Outliers', isLtr); + } + } else { + if (point.minimum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += + _formatTrackballLabel(point.minimum!, digits, 'Minimum', isLtr); + } + + if (point.maximum != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += + _formatTrackballLabel(point.maximum!, digits, 'Maximum', isLtr); + } + + if (point.median != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.median!, digits, 'Median', isLtr); + } + + if (point.mean != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel(point.mean!, digits, 'Mean', isLtr); + } + + if (point.lowerQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.lowerQuartile!, digits, 'LowerQuartile', isLtr); + } + + if (point.upperQuartile != null) { + if (text.isNotEmpty) { + text += '\n'; + } + + text += _formatTrackballLabel( + point.upperQuartile!, digits, 'UpperQuartile', isLtr); + } + } + } + + return text; + } + + String tooltipHeaderText(CartesianChartPoint point, [int digits = 3]) { + String text = ''; + if (xAxis is RenderNumericAxis || xAxis is RenderLogarithmicAxis) { + text = formatNumericValue(point.x! as num, xAxis, digits); + } else if (xAxis is RenderDateTimeAxis) { + final RenderDateTimeAxis axis = xAxis! as RenderDateTimeAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderDateTimeCategoryAxis) { + final RenderDateTimeCategoryAxis axis = + xAxis! as RenderDateTimeCategoryAxis; + final DateFormat dateFormat = + axis.dateFormat ?? _dateTimeLabelFormat(xAxis!); + text = dateFormat.format(point.x! as DateTime); + } else if (xAxis is RenderCategoryAxis) { + text = point.x!.toString(); + } + + return text; + } + + DateFormat _dateTimeLabelFormat(RenderChartAxis axis, + [int? interval, int? prevInterval]) { + DateFormat? format; + final bool notDoubleInterval = + (axis.interval != null && axis.interval! % 1 == 0) || + axis.interval == null; + DateTimeIntervalType? actualIntervalType; + num? minimum; + minimum = axis.visibleRange!.minimum; + if (axis is RenderDateTimeAxis) { + actualIntervalType = axis.visibleIntervalType; + } else if (axis is RenderDateTimeCategoryAxis) { + actualIntervalType = axis.visibleIntervalType; + } + + switch (actualIntervalType!) { + case DateTimeIntervalType.years: + format = notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); + break; + case DateTimeIntervalType.months: + format = (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + + break; + case DateTimeIntervalType.days: + format = (minimum == interval || interval == prevInterval) + ? _firstLabelFormat(actualIntervalType) + : _dateTimeFormat(actualIntervalType, interval, prevInterval); + break; + case DateTimeIntervalType.hours: + format = DateFormat.j(); + break; + case DateTimeIntervalType.minutes: + format = DateFormat.Hm(); + break; + case DateTimeIntervalType.seconds: + format = DateFormat.ms(); + break; + case DateTimeIntervalType.milliseconds: + final DateFormat dateFormat = DateFormat('ss.SSS'); + format = dateFormat; + break; + case DateTimeIntervalType.auto: + format ??= DateFormat(); + break; + } + return format!; + } + + DateFormat? _dateTimeFormat(DateTimeIntervalType? actualIntervalType, + int? interval, int? prevInterval) { + final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(interval!); + final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(prevInterval!); + DateFormat? format; + final bool isIntervalDecimal = interval % 1 == 0; + if (actualIntervalType == DateTimeIntervalType.months) { + format = minimum.year == maximum.year + ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) + : DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = minimum.month != maximum.month + ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) + : DateFormat.d(); + } + + return format; + } + + DateFormat? _firstLabelFormat(DateTimeIntervalType? actualIntervalType) { + DateFormat? format; + + if (actualIntervalType == DateTimeIntervalType.months) { + format = DateFormat('yyy MMM'); + } else if (actualIntervalType == DateTimeIntervalType.days) { + format = DateFormat.MMMd(); + } else if (actualIntervalType == DateTimeIntervalType.minutes) { + format = DateFormat.Hm(); + } + + return format; + } + + Offset translateTransform(num x, num y, + [double translationX = 0, double translationY = 0]) { + final double posX = pointToPixelX(x, y); + final double posY = pointToPixelY(x, y); + return Offset(posX + translationX, posY + translationY); + } + + ChartMarker markerAt(int pointIndex) { + if (markerContainer != null) { + return markerContainer!.markerAt(pointIndex); + } + return ChartMarker(); + } + + Shader? markerShader(Rect bounds) { + if (onCreateShader != null) { + final ShaderDetails details = ShaderDetails(bounds, 'marker'); + return onCreateShader!(details); + } else if (gradient != null) { + return gradient!.createShader(bounds); + } + return null; + } +} + +extension IndicatorExtension on IndicatorRenderer { + String _replace( + String tooltipText, String replacingText, num value, int digits) { + return tooltipText.replaceAll( + replacingText, formatNumericValue(value, yAxis, digits)); + } + + String _formatTrackballLabel(num value, int digits, String text, bool isLtr) { + if (text.isEmpty) { + return formatNumericValue(value, yAxis, digits); + } + return isLtr + ? '$text: ${formatNumericValue(value, yAxis, digits)}' + : '${formatNumericValue(value, yAxis, digits)} :$text'; + } + + String trackballText(CartesianChartPoint point, String seriesName, + {int outlierIndex = -1}) { + if (parent == null || + parent!.trackballBehavior == null || + xAxis == null || + point.x == null) { return ''; } - final int digits = parent!.tooltipBehavior!.decimalPlaces; - if (!parent!.tooltipBehavior!.shared) { - text = tooltipHeaderText(point, digits); + String text = ''; + final int digits = parent!.trackballBehavior!.tooltipSettings.decimalPlaces; + if (parent!.trackballBehavior!.tooltipDisplayMode == + TrackballDisplayMode.groupAllPoints) { + text = '$seriesName '; } - final bool isLtr = textDirection == TextDirection.ltr; - final String? tooltipFormat = parent?.tooltipBehavior?.format; + return _behaviorText( + parent!.trackballBehavior!.tooltipSettings.format, + text, + point, + digits, + outlierIndex, + seriesName, + parent!.textDirection == TextDirection.ltr); + } + + String _behaviorText( + String? tooltipFormat, + String text, + CartesianChartPoint point, + int digits, + int outlierIndex, + String seriesName, + bool isLtr, + ) { if (tooltipFormat != null) { + text = tooltipHeaderText(point, digits); String tooltipText = tooltipFormat.replaceAll('point.x', text); if (point.y != null) { @@ -1027,18 +1588,18 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { _replace(tooltipText, 'point.size', point.bubbleSize!, digits); } - tooltipText = tooltipText.replaceAll('series.name', name); + tooltipText = tooltipText.replaceAll('series.name', seriesName); text = isLtr ? tooltipText : formatRTLText(tooltipText); } else { if (point.y != null) { - text = _formatTooltipLabel(point.y!, digits, text, isLtr); + text = _formatTrackballLabel(point.y!, digits, text, isLtr); } if (point.high != null) { if (text.isNotEmpty) { text += '\n'; } - text += _formatTooltipLabel(point.high!, digits, 'High', isLtr); + text += _formatTrackballLabel(point.high!, digits, 'High', isLtr); } if (point.low != null) { @@ -1046,21 +1607,21 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel(point.low!, digits, 'Low', isLtr); + text += _formatTrackballLabel(point.low!, digits, 'Low', isLtr); } if (point.open != null) { if (text.isNotEmpty) { text += '\n'; } - text += _formatTooltipLabel(point.open!, digits, 'Open', isLtr); + text += _formatTrackballLabel(point.open!, digits, 'Open', isLtr); } if (point.close != null) { if (text.isNotEmpty) { text += '\n'; } - text += _formatTooltipLabel(point.close!, digits, 'Close', isLtr); + text += _formatTrackballLabel(point.close!, digits, 'Close', isLtr); } if (outlierIndex != -1) { @@ -1069,7 +1630,7 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel( + text += _formatTrackballLabel( point.outliers![outlierIndex], digits, 'Outliers', isLtr); } } else { @@ -1078,7 +1639,8 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel(point.minimum!, digits, 'Minimum', isLtr); + text += + _formatTrackballLabel(point.minimum!, digits, 'Minimum', isLtr); } if (point.maximum != null) { @@ -1086,7 +1648,8 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel(point.maximum!, digits, 'Maximum', isLtr); + text += + _formatTrackballLabel(point.maximum!, digits, 'Maximum', isLtr); } if (point.median != null) { @@ -1094,7 +1657,7 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel(point.median!, digits, 'Median', isLtr); + text += _formatTrackballLabel(point.median!, digits, 'Median', isLtr); } if (point.mean != null) { @@ -1102,7 +1665,7 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += _formatTooltipLabel(point.mean!, digits, 'Mean', isLtr); + text += _formatTrackballLabel(point.mean!, digits, 'Mean', isLtr); } if (point.lowerQuartile != null) { @@ -1110,8 +1673,8 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += - _formatTooltipLabel(point.lowerQuartile!, digits, 'LQ', isLtr); + text += _formatTrackballLabel( + point.lowerQuartile!, digits, 'LowerQuartile', isLtr); } if (point.upperQuartile != null) { @@ -1119,12 +1682,11 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { text += '\n'; } - text += - _formatTooltipLabel(point.upperQuartile!, digits, 'HQ', isLtr); + text += _formatTrackballLabel( + point.upperQuartile!, digits, 'UpperQuartile', isLtr); } } } - return text; } @@ -1194,6 +1756,7 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { format = dateFormat; break; case DateTimeIntervalType.auto: + format ??= DateFormat(); break; } return format!; @@ -1231,30 +1794,6 @@ extension CartesianSeriesExtension on CartesianSeriesRenderer { return format; } - - Offset translateTransform(num x, num y, - [double translationX = 0, double translationY = 0]) { - final double posX = pointToPixelX(x, y); - final double posY = pointToPixelY(x, y); - return Offset(posX + translationX, posY + translationY); - } - - ChartMarker markerAt(int pointIndex) { - if (markerContainer != null) { - return markerContainer!.markerAt(pointIndex); - } - return ChartMarker(); - } - - Shader? markerShader(Rect bounds) { - if (onCreateShader != null) { - final ShaderDetails details = ShaderDetails(bounds, 'marker'); - return onCreateShader!(details); - } else if (gradient != null) { - return gradient!.createShader(bounds); - } - return null; - } } int nextIndexConsideringEmptyPointMode( @@ -1445,6 +1984,12 @@ Widget buildLegendItem( } else { point = ChartPoint(x: item.text); } + + if (item.series is! CartesianSeriesRenderer && + item.series!.segments.isNotEmpty) { + point.isVisible = item.series!.segmentAt(item.pointIndex).isVisible; + } + return legend.legendItemBuilder!( item.text, item.series?.widget, @@ -1452,3 +1997,306 @@ Widget buildLegendItem( item.series is CartesianSeriesRenderer ? item.seriesIndex : item.pointIndex, ); } + +mixin Stacking100SeriesMixin {} + +bool isValueLinear(int index, num value, List values) { + final int length = values.length; + if (length == 0) { + return true; + } + + if (index == 0) { + return length == 1 || value <= values[index + 1]; + } + + if (index == length - 1) { + return value >= values[index - 1]; + } + + return value >= values[index - 1] && value <= values[index + 1]; +} + +DateFormat dateTimeAxisLabelFormat( + RenderDateTimeAxis axis, num current, int previous) { + return _niceDateFormat(current, previous, axis.visibleRange!.minimum, + axis.interval, axis.visibleInterval, axis.visibleIntervalType); +} + +DateFormat dateTimeCategoryAxisLabelFormat( + RenderDateTimeCategoryAxis axis, num current, int previous) { + return _niceDateFormat(current, previous, axis.visibleRange!.minimum, + axis.interval, axis.visibleInterval, axis.visibleIntervalType); +} + +DateFormat _niceDateFormat(num current, int previous, num minimum, + double? interval, num visibleInterval, DateTimeIntervalType intervalType) { + final bool notDoubleInterval = + (interval != null && interval % 1 == 0) || interval == null; + switch (intervalType) { + case DateTimeIntervalType.years: + return notDoubleInterval ? DateFormat.y() : DateFormat.MMMd(); + + case DateTimeIntervalType.months: + return (minimum == current || current == previous) + ? _firstLabelFormat(intervalType) + : _normalDateFormat(intervalType, visibleInterval, current, previous); + + case DateTimeIntervalType.days: + return (minimum == current || current == previous) + ? _firstLabelFormat(intervalType) + : _normalDateFormat(intervalType, visibleInterval, current, previous); + + case DateTimeIntervalType.hours: + return DateFormat.j(); + + case DateTimeIntervalType.minutes: + return DateFormat.Hm(); + + case DateTimeIntervalType.seconds: + return DateFormat.ms(); + + case DateTimeIntervalType.milliseconds: + return DateFormat('ss.SSS'); + + case DateTimeIntervalType.auto: + return DateFormat(); + } +} + +DateFormat _firstLabelFormat(DateTimeIntervalType visibleIntervalType) { + if (visibleIntervalType == DateTimeIntervalType.months) { + return DateFormat('yyy MMM'); + } else if (visibleIntervalType == DateTimeIntervalType.days) { + return DateFormat.MMMd(); + } else if (visibleIntervalType == DateTimeIntervalType.minutes) { + return DateFormat.Hm(); + } else { + return DateFormat(); + } +} + +DateFormat _normalDateFormat(DateTimeIntervalType visibleIntervalType, + num visibleInterval, num current, int previousLabel) { + final DateTime minimum = DateTime.fromMillisecondsSinceEpoch(current.toInt()); + final DateTime maximum = DateTime.fromMillisecondsSinceEpoch(previousLabel); + final bool isIntervalDecimal = visibleInterval % 1 == 0; + if (visibleIntervalType == DateTimeIntervalType.months) { + return minimum.year == maximum.year + ? (isIntervalDecimal ? DateFormat.MMM() : DateFormat.MMMd()) + : DateFormat('yyy MMM'); + } else if (visibleIntervalType == DateTimeIntervalType.days) { + return minimum.month != maximum.month + ? (isIntervalDecimal ? DateFormat.MMMd() : DateFormat.MEd()) + : DateFormat.d(); + } else { + return DateFormat(); + } +} + +String numericAxisLabel(RenderNumericAxis axis, num value, int showDigits) { + return _labelValue(value, showDigits, axis.numberFormat, axis.labelFormat); +} + +String logAxisLabel(RenderLogarithmicAxis axis, num value, int showDigits) { + return _labelValue(value, showDigits, axis.numberFormat, axis.labelFormat); +} + +String _labelValue(num value, int showDigits, NumberFormat? numberFormat, + String? labelFormat) { + final List pieces = value.toString().split('.'); + if (pieces.length > 1) { + value = double.parse(value.toStringAsFixed(showDigits)); + final String decimals = pieces[1]; + final bool isDecimalContainsZero = decimals == '0' || + decimals == '00' || + decimals == '000' || + decimals == '0000' || + decimals == '00000' || + value % 1 == 0; + value = isDecimalContainsZero ? value.round() : value; + } + + String text = value.toString(); + if (numberFormat != null) { + text = numberFormat.format(value); + } + if (labelFormat != null && labelFormat != '') { + text = labelFormat.replaceAll(RegExp('{value}'), text); + } + return text; +} + +RRect performLegendToggleAnimation( + SbsSeriesMixin series, + RRect segmentRect, + RRect oldSegmentRect, + BorderRadius borderRadius, +) { + final double animationFactor = series.segmentAnimationFactor; + final bool oldSeriesVisible = series.visibilityBeforeTogglingLegend; + + if (series.parent!.isTransposed) { + return performTransposedLegendToggleAnimation(series, segmentRect, + oldSegmentRect, oldSeriesVisible, animationFactor, borderRadius); + } + + final RenderCartesianChartPlotArea plotArea = series.parent!; + final CartesianSeriesRenderer firstSeries = + plotArea.firstChild as CartesianSeriesRenderer; + final CartesianSeriesRenderer lastSeries = + plotArea.lastChild as CartesianSeriesRenderer; + + final bool isSingleBarSeries = _isSingleBarSeries(plotArea); + num right = 0; + final double height = segmentRect.height; + double width = segmentRect.width; + double left = segmentRect.left; + final double top = segmentRect.top; + + /// Left and right animation handled when toggling the legend. + if (oldSeriesVisible) { + final double oldRight = oldSegmentRect.right; + final double oldLeft = oldSegmentRect.left; + final double newRight = segmentRect.right; + final double newLeft = segmentRect.left; + + right = oldRight > newRight + ? oldRight + (animationFactor * (newRight - oldRight)) + : oldRight - (animationFactor * (oldRight - newRight)); + left = oldLeft > newLeft + ? oldLeft - (animationFactor * (oldLeft - newLeft)) + : oldLeft + (animationFactor * (newLeft - oldLeft)); + width = right - left; + } else { + final bool isInversed = series.xAxis!.isInversed; + if (series == firstSeries && !isSingleBarSeries) { + /// Handled the left to right side animation when re-toggling the first series legend. + if (isInversed) { + right = segmentRect.right; + left = right - (segmentRect.width * animationFactor); + width = right - left; + } else { + left = segmentRect.left; + width = segmentRect.width * animationFactor; + } + } else if (series == lastSeries && !isSingleBarSeries) { + /// Handled the right to left side animation when re-toggling the last series legend. + if (isInversed) { + left = segmentRect.left; + width = segmentRect.width * animationFactor; + } else { + right = segmentRect.right; + left = right - (segmentRect.width * animationFactor); + width = right - left; + } + } else { + /// Handled width animation when re-toggling middle series legend. + width = segmentRect.width * animationFactor; + left = segmentRect.center.dx - width / 2; + } + } + + return RRect.fromRectAndCorners( + Rect.fromLTWH(left, top, width, height), + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); +} + +RRect performTransposedLegendToggleAnimation( + SbsSeriesMixin series, + RRect segmentRect, + RRect oldSegmentRect, + bool oldSeriesVisible, + double animationFactor, + BorderRadius borderRadius, +) { + final RenderCartesianChartPlotArea plotArea = series.parent!; + final CartesianSeriesRenderer firstSeries = + plotArea.firstChild as CartesianSeriesRenderer; + final CartesianSeriesRenderer lastSeries = + plotArea.lastChild as CartesianSeriesRenderer; + + final bool isSingleBarSeries = _isSingleBarSeries(plotArea); + num bottom; + double height = segmentRect.height; + double top = segmentRect.top; + final double width = segmentRect.width; + final double left = segmentRect.left; + + /// Handled top and bottom animation when toggling the legend. + if (oldSeriesVisible) { + final double oldBottom = oldSegmentRect.bottom; + final double oldTop = oldSegmentRect.top; + final double newBottom = segmentRect.bottom; + final double newTop = segmentRect.top; + + bottom = oldBottom > newBottom + ? oldBottom + (animationFactor * (newBottom - oldBottom)) + : oldBottom - (animationFactor * (oldBottom - newBottom)); + top = oldTop > newTop + ? oldTop - (animationFactor * (oldTop - newTop)) + : oldTop + (animationFactor * (newTop - oldTop)); + height = bottom - top; + } else { + final bool isInversed = series.xAxis!.isInversed; + if (series == firstSeries && !isSingleBarSeries) { + /// Handled the bottom to top side animation when re-toggling the first series legend. + if (isInversed) { + top = segmentRect.top; + height = segmentRect.height * animationFactor; + } else { + bottom = segmentRect.bottom; + top = bottom - (segmentRect.height * animationFactor); + height = bottom - top; + } + } else if (series == lastSeries && !isSingleBarSeries) { + /// Handled the top to bottom side animation when re-toggling the last series legend. + if (isInversed) { + bottom = segmentRect.bottom; + top = bottom - (segmentRect.height * animationFactor); + height = bottom - top; + } else { + top = segmentRect.top; + height = segmentRect.height * animationFactor; + } + } else { + /// Handled height animation when re-toggling middle series legend. + height = segmentRect.height * animationFactor; + top = segmentRect.center.dy - height / 2; + } + } + + return RRect.fromRectAndCorners( + Rect.fromLTWH(left, top, width, height), + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); +} + +bool _isSingleBarSeries(RenderCartesianChartPlotArea plotArea) { + int count = 0; + plotArea.visitChildren((child) { + if (child is SbsSeriesMixin && child.controller.isVisible) { + count++; + } + }); + return count == 1; +} + +void animateAllBarSeries(RenderCartesianChartPlotArea plotArea) { + plotArea.isLegendToggled = true; + plotArea.visitChildren((child) { + if (child is CartesianSeriesRenderer) { + if ((child is ColumnSeriesRenderer || child is BarSeriesRenderer) && + child.animationType == AnimationType.none) { + child.animationType = AnimationType.realtime; + } + } + }); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart b/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart index 51f5c6fdd..07644c464 100644 --- a/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/charts/utils/renderer_helper.dart @@ -75,6 +75,18 @@ Path calculateArcPath(double innerRadius, double radius, Offset center, return path; } +/// Calculate series start or end angle based on animation type. +double calculateAngle(bool isRealTimeAnimation, int startAngle, int endAngle) { + // Segment animation + if (isRealTimeAnimation) { + final int finalEndAngle = + startAngle == endAngle ? 360 + endAngle : endAngle; + return finalEndAngle - 90; + } + // Series animation + return startAngle - 90; +} + /// Calculate rounded corners arc path. Path calculateRoundedCornerArcPath(CornerStyle cornerStyle, double innerRadius, double outerRadius, Offset center, double startAngle, double endAngle) { diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart index cf5c0d365..3b4b1d29a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/renderer_base.dart @@ -86,7 +86,7 @@ abstract class SfSparkChartRenderObjectWidget extends LeafRenderObjectWidget { final SparkChartDataDetails? sparkChartDataDetails; /// Specfies the theme of the spark chart. - final SfChartThemeData? themeData; + final SfSparkChartThemeData? themeData; /// Specifies the series screen coordinate points. final List? coordinatePoints; @@ -119,7 +119,7 @@ abstract class RenderSparkChart extends RenderBox { Color? negativePointColor, SparkChartPlotBand? plotBand, SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, List? coordinatePoints, List? dataPoints}) : _data = data, @@ -268,13 +268,13 @@ abstract class RenderSparkChart extends RenderBox { } /// Defines the spark chart theme. - SfChartThemeData? _themeData; + SfSparkChartThemeData? _themeData; /// Returns the spark chart theme. - SfChartThemeData? get themeData => _themeData; + SfSparkChartThemeData? get themeData => _themeData; /// Sets the spark chart theme. - set themeData(SfChartThemeData? value) { + set themeData(SfSparkChartThemeData? value) { if (_themeData != value) { _themeData = value; markNeedsPaint(); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart index d410e6ac4..549ce9f54 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_area_renderer.dart @@ -33,7 +33,7 @@ class SfSparkAreaChartRenderObjectWidget this.marker, this.labelDisplayMode, this.labelStyle, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, SparkChartDataDetails? sparkChartDataDetails, List? coordinatePoints, List? dataPoints, @@ -164,7 +164,7 @@ class _RenderSparkAreaChart extends RenderSparkChart { SparkChartLabelDisplayMode? labelDisplayMode, TextStyle? labelStyle, SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, List? coordinatePoints, List? dataPoints}) : _borderWidth = borderWidth, diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart index c14c5d0aa..17d807b8a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_bar_renderer.dart @@ -30,7 +30,7 @@ class SfSparkBarChartRenderObjectWidget extends SfSparkChartRenderObjectWidget { SparkChartPlotBand? plotBand, this.labelDisplayMode, this.labelStyle, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, SparkChartDataDetails? sparkChartDataDetails, List? coordinatePoints, List? dataPoints}) @@ -155,7 +155,7 @@ class _RenderSparkBarChart extends RenderSparkChart { SparkChartLabelDisplayMode? labelDisplayMode, TextStyle? labelStyle, SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, List? coordinatePoints, List? dataPoints}) : _borderWidth = borderWidth, diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart index c120eb7b8..25b9f46bf 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_line_renderer.dart @@ -33,7 +33,7 @@ class SfSparkLineChartRenderObjectWidget this.marker, this.labelDisplayMode, this.labelStyle, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, SparkChartDataDetails? sparkChartDataDetails, List? coordinatePoints, List? dataPoints}) @@ -164,7 +164,7 @@ class _RenderSparkLineChart extends RenderSparkChart { SparkChartLabelDisplayMode? labelDisplayMode, TextStyle? labelStyle, SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, List? coordinatePoints, List? dataPoints}) : _width = width, diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart index d2e1c2f66..f01a56b26 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/renderers/spark_win_loss_renderer.dart @@ -31,7 +31,7 @@ class SfSparkWinLossChartRenderObjectWidget Color? lastPointColor, Color? negativePointColor, SparkChartDataDetails? sparkChartDataDetails, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, List? coordinatePoints, List? dataPoints}) : super( @@ -148,7 +148,7 @@ class _RenderSparkWinLossChart extends RenderSparkChart { Color? tiePointColor, double? borderWidth, Color? borderColor, - SfChartThemeData? themeData, + SfSparkChartThemeData? themeData, SparkChartDataDetails? sparkChartDataDetails, List? coordinatePoints, List? dataPoints}) diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart index 8e26089a9..610caab82 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_area_base.dart @@ -3,6 +3,7 @@ import 'package:syncfusion_flutter_core/theme.dart'; import '../marker.dart'; import '../plot_band.dart'; import '../renderers/spark_area_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -44,10 +45,10 @@ class SfSparkAreaChart extends StatefulWidget { this.plotBand, this.borderWidth = 0, this.borderColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -57,11 +58,7 @@ class SfSparkAreaChart extends StatefulWidget { this.lastPointColor, this.marker, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); @@ -132,10 +129,10 @@ class SfSparkAreaChart extends StatefulWidget { this.plotBand, this.borderWidth = 2, this.borderColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -145,11 +142,7 @@ class SfSparkAreaChart extends StatefulWidget { this.lastPointColor, this.marker, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails( dataCount: dataCount, @@ -222,7 +215,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Customizes the color of the axis line. /// Colors.transparent can be set to [axisLineColor] to hide the axis line. /// - /// Defaults to `Colors.black`. + /// Defaults to null. /// /// ```dart /// @override @@ -237,7 +230,7 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// Dashes of the axis line. Any number of values can be provided on the list. /// Odd value is considered as rendering size and even value is considered a gap. @@ -372,7 +365,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Customizes the spark area chart color. /// - /// Defaults to `Colors.blue`. + /// Defaults to null. /// /// ```dart /// @override @@ -386,7 +379,7 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -522,8 +515,7 @@ class SfSparkAreaChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and font - /// family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -539,7 +531,7 @@ class SfSparkAreaChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Enables and customizes the trackball. /// @@ -579,7 +571,7 @@ class SfSparkAreaChart extends StatefulWidget { /// Represents the state class for spark area widget. class _SfSparkAreaChartState extends State { /// Specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -587,6 +579,44 @@ class _SfSparkAreaChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = theme.useMaterial3 + ? SfSparkChartThemeDataM3(context) + : SfSparkChartThemeDataM2(context); + chartThemeData = chartThemeData.copyWith( + color: widget.color ?? + chartThemeData.color ?? + effectiveChartThemeData.color, + axisLineColor: widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle)); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -628,7 +658,6 @@ class _SfSparkAreaChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -643,6 +672,7 @@ class _SfSparkAreaChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); if (widget.marker != null && widget.marker!.displayMode != SparkChartMarkerDisplayMode.none) { final double padding = widget.marker!.size / 2; @@ -665,7 +695,7 @@ class _SfSparkAreaChartState extends State { yValueMapper: widget._sparkChartDataDetails.yValueMapper, isInversed: widget.isInversed, axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, + axisLineColor: _chartThemeData.axisLineColor, axisLineWidth: widget.axisLineWidth, axisLineDashArray: widget.axisLineDashArray, highPointColor: widget.highPointColor, @@ -673,13 +703,13 @@ class _SfSparkAreaChartState extends State { firstPointColor: widget.firstPointColor, lastPointColor: widget.lastPointColor, negativePointColor: widget.negativePointColor, - color: widget.color, + color: _chartThemeData.color, borderColor: widget.borderColor, borderWidth: widget.borderWidth, plotBand: widget.plotBand, marker: widget.marker, labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, + labelStyle: _chartThemeData.dataLabelTextStyle, themeData: _chartThemeData, sparkChartDataDetails: widget._sparkChartDataDetails, dataPoints: _dataPoints, @@ -688,6 +718,7 @@ class _SfSparkAreaChartState extends State { trackball: widget.trackball, coordinatePoints: _coordinatePoints, dataPoints: _dataPoints, + themeData: _chartThemeData, sparkChart: widget) ])); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart index d24d2792c..8e97c3f4c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_bar_base.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../plot_band.dart'; import '../renderers/spark_bar_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -42,10 +43,10 @@ class SfSparkBarChart extends StatefulWidget { this.plotBand, this.borderWidth = 0, this.borderColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -54,11 +55,7 @@ class SfSparkBarChart extends StatefulWidget { this.firstPointColor, this.lastPointColor, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); @@ -128,10 +125,10 @@ class SfSparkBarChart extends StatefulWidget { this.plotBand, this.borderWidth = 2, this.borderColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -140,11 +137,7 @@ class SfSparkBarChart extends StatefulWidget { this.firstPointColor, this.lastPointColor, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails( dataCount: dataCount, @@ -233,7 +226,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// Dashes of the axis line. Any number of values can be provided on the list. /// Odd value is considered as rendering size and even value is considered a gap. @@ -376,7 +369,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -482,8 +475,7 @@ class SfSparkBarChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` - /// and font family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -498,7 +490,7 @@ class SfSparkBarChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Enables and customizes the trackball. /// @@ -537,7 +529,7 @@ class SfSparkBarChart extends StatefulWidget { /// Represents the state class for spark bar widget. class _SfSparkBarChartState extends State { /// specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -545,6 +537,44 @@ class _SfSparkBarChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = theme.useMaterial3 + ? SfSparkChartThemeDataM3(context) + : SfSparkChartThemeDataM2(context); + chartThemeData = chartThemeData.copyWith( + color: widget.color ?? + chartThemeData.color ?? + effectiveChartThemeData.color, + axisLineColor: widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle)); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -570,7 +600,6 @@ class _SfSparkBarChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -601,6 +630,7 @@ class _SfSparkBarChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); return RepaintBoundary( child: SparkChartContainer( child: Stack(children: [ @@ -611,7 +641,7 @@ class _SfSparkBarChartState extends State { yValueMapper: widget._sparkChartDataDetails.yValueMapper, isInversed: widget.isInversed, axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, + axisLineColor: _chartThemeData.axisLineColor, axisLineWidth: widget.axisLineWidth, axisLineDashArray: widget.axisLineDashArray, highPointColor: widget.highPointColor, @@ -619,12 +649,12 @@ class _SfSparkBarChartState extends State { firstPointColor: widget.firstPointColor, lastPointColor: widget.lastPointColor, negativePointColor: widget.negativePointColor, - color: widget.color, + color: _chartThemeData.color, borderColor: widget.borderColor, borderWidth: widget.borderWidth, plotBand: widget.plotBand, labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, + labelStyle: _chartThemeData.dataLabelTextStyle, themeData: _chartThemeData, sparkChartDataDetails: widget._sparkChartDataDetails, dataPoints: _dataPoints, @@ -633,6 +663,7 @@ class _SfSparkBarChartState extends State { trackball: widget.trackball, coordinatePoints: _coordinatePoints, dataPoints: _dataPoints, + themeData: _chartThemeData, sparkChart: widget, ) ]))); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart index e199cf5e7..99c2c26dc 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_line_base.dart @@ -3,6 +3,7 @@ import 'package:syncfusion_flutter_core/theme.dart'; import '../marker.dart'; import '../plot_band.dart'; import '../renderers/spark_line_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -43,10 +44,10 @@ class SfSparkLineChart extends StatefulWidget { this.plotBand, this.width = 2, this.dashArray, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -56,11 +57,7 @@ class SfSparkLineChart extends StatefulWidget { this.lastPointColor, this.marker, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.trackball}) : _sparkChartDataDetails = SparkChartDataDetails(data: data), super(key: key); @@ -131,10 +128,10 @@ class SfSparkLineChart extends StatefulWidget { this.plotBand, this.width = 2, this.dashArray, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -145,11 +142,7 @@ class SfSparkLineChart extends StatefulWidget { this.trackball, this.marker, this.labelDisplayMode, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12)}) + this.labelStyle}) : _sparkChartDataDetails = SparkChartDataDetails( dataCount: dataCount, xValueMapper: xValueMapper, @@ -222,7 +215,7 @@ class SfSparkLineChart extends StatefulWidget { /// Customizes the color of the axis line. /// Colors.transparent can be set to [axisLineColor] to hide the axis line. /// - /// Defaults to `Colors.black`. + /// Defaults to null. /// /// ```dart /// @override @@ -237,7 +230,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// Dashes of the axis line. Any number of values can be provided on the list. /// Odd value is considered as rendering size and even value is considered a gap. @@ -372,7 +365,7 @@ class SfSparkLineChart extends StatefulWidget { /// Customizes the spark line chart color. /// - /// Defaults to `Colors.blue`. + /// Defaults to null. /// /// ```dart /// @override @@ -386,7 +379,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -542,8 +535,7 @@ class SfSparkLineChart extends StatefulWidget { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and font family - /// `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -559,7 +551,7 @@ class SfSparkLineChart extends StatefulWidget { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Specifies the spark chart data details. final SparkChartDataDetails _sparkChartDataDetails; @@ -573,7 +565,7 @@ class SfSparkLineChart extends StatefulWidget { /// Represents the state class for spark line chart widget. class _SfSparkLineChartState extends State { /// Specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -581,6 +573,44 @@ class _SfSparkLineChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = theme.useMaterial3 + ? SfSparkChartThemeDataM3(context) + : SfSparkChartThemeDataM2(context); + chartThemeData = chartThemeData.copyWith( + color: widget.color ?? + chartThemeData.color ?? + effectiveChartThemeData.color, + axisLineColor: widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + dataLabelTextStyle: theme.textTheme.bodySmall! + .copyWith(color: Colors.transparent) + .merge(chartThemeData.dataLabelTextStyle) + .merge(widget.labelStyle), + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle)); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -606,7 +636,6 @@ class _SfSparkLineChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -650,6 +679,7 @@ class _SfSparkLineChartState extends State { /// Method to return the spark line chart widget. Widget _getSparkLineChart() { + _chartThemeData = _updateThemeData(context); return SparkChartContainer( child: Stack(children: [ SfSparkLineChartRenderObjectWidget( @@ -661,7 +691,7 @@ class _SfSparkLineChartState extends State { dashArray: widget.dashArray, isInversed: widget.isInversed, axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, + axisLineColor: _chartThemeData.axisLineColor, axisLineWidth: widget.axisLineWidth, axisLineDashArray: widget.axisLineDashArray, highPointColor: widget.highPointColor, @@ -669,11 +699,11 @@ class _SfSparkLineChartState extends State { firstPointColor: widget.firstPointColor, lastPointColor: widget.lastPointColor, negativePointColor: widget.negativePointColor, - color: widget.color, + color: _chartThemeData.color, plotBand: widget.plotBand, marker: widget.marker, labelDisplayMode: widget.labelDisplayMode, - labelStyle: widget.labelStyle, + labelStyle: _chartThemeData.dataLabelTextStyle, themeData: _chartThemeData, sparkChartDataDetails: widget._sparkChartDataDetails, dataPoints: _dataPoints, @@ -682,6 +712,7 @@ class _SfSparkLineChartState extends State { trackball: widget.trackball, coordinatePoints: _coordinatePoints, dataPoints: _dataPoints, + themeData: _chartThemeData, ) ])); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart index d4d5272de..7ef948a6e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/series/spark_win_loss_base.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../plot_band.dart'; import '../renderers/spark_win_loss_renderer.dart'; +import '../theme.dart'; import '../trackball/spark_chart_trackball.dart'; import '../trackball/trackball_renderer.dart'; import '../utils/enum.dart'; @@ -45,10 +46,10 @@ class SfSparkWinLossChart extends StatefulWidget { this.borderWidth = 0, this.borderColor, this.tiePointColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -127,10 +128,10 @@ class SfSparkWinLossChart extends StatefulWidget { this.borderWidth = 2, this.borderColor, this.tiePointColor, - this.color = Colors.blue, + this.color, this.isInversed = false, this.axisCrossesAt = 0, - this.axisLineColor = Colors.black, + this.axisLineColor, this.axisLineWidth = 2, this.axisLineDashArray, this.highPointColor, @@ -226,7 +227,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color axisLineColor; + final Color? axisLineColor; /// Dashes of the axis line. Any number of values can be provided on the list. /// Odd value is considered as rendering size and even value is considered a gap. @@ -370,7 +371,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// ); /// } /// ``` - final Color color; + final Color? color; /// Render plot band. /// @@ -499,7 +500,7 @@ class SfSparkWinLossChart extends StatefulWidget { /// Represents the state class for spark win loss chart widget. class _SfSparkWinLossChartState extends State { /// specifies the theme of the chart. - late SfChartThemeData _chartThemeData; + late SfSparkChartThemeData _chartThemeData; /// Specifies the series screen coordinate points. late List _coordinatePoints; @@ -507,6 +508,40 @@ class _SfSparkWinLossChartState extends State { /// Specifies the series data points. late List _dataPoints; + SfSparkChartThemeData _updateThemeData(BuildContext context) { + SfSparkChartThemeData chartThemeData = SfSparkChartTheme.of(context); + final ThemeData theme = Theme.of(context); + final SfSparkChartThemeData effectiveChartThemeData = theme.useMaterial3 + ? SfSparkChartThemeDataM3(context) + : SfSparkChartThemeDataM2(context); + chartThemeData = chartThemeData.copyWith( + color: widget.color ?? + chartThemeData.color ?? + effectiveChartThemeData.color, + axisLineColor: widget.axisLineColor ?? + chartThemeData.axisLineColor ?? + effectiveChartThemeData.axisLineColor, + markerFillColor: chartThemeData.markerFillColor ?? + effectiveChartThemeData.markerFillColor, + dataLabelBackgroundColor: chartThemeData.dataLabelBackgroundColor ?? + effectiveChartThemeData.dataLabelBackgroundColor, + tooltipColor: + chartThemeData.tooltipColor ?? effectiveChartThemeData.tooltipColor, + trackballLineColor: chartThemeData.trackballLineColor ?? + effectiveChartThemeData.trackballLineColor, + tooltipLabelColor: chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + trackballTextStyle: theme.textTheme.bodySmall + ?.copyWith( + color: widget.trackball?.color ?? + chartThemeData.tooltipLabelColor ?? + effectiveChartThemeData.tooltipLabelColor, + ) + .merge(chartThemeData.trackballTextStyle) + .merge(widget.trackball?.labelStyle)); + return chartThemeData; + } + /// Called when this object is inserted into the tree. /// /// The framework will call this method exactly once for each State object it creates. @@ -532,7 +567,6 @@ class _SfSparkWinLossChartState extends State { @override void didChangeDependencies() { - _chartThemeData = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -563,6 +597,7 @@ class _SfSparkWinLossChartState extends State { @override Widget build(BuildContext context) { + _chartThemeData = _updateThemeData(context); return RepaintBoundary( child: SparkChartContainer( child: Stack(children: [ @@ -573,7 +608,7 @@ class _SfSparkWinLossChartState extends State { yValueMapper: widget._sparkChartDataDetails.yValueMapper, isInversed: widget.isInversed, axisCrossesAt: widget.axisCrossesAt, - axisLineColor: widget.axisLineColor, + axisLineColor: _chartThemeData.axisLineColor, axisLineWidth: widget.axisLineWidth, axisLineDashArray: widget.axisLineDashArray, highPointColor: widget.highPointColor, @@ -581,7 +616,7 @@ class _SfSparkWinLossChartState extends State { firstPointColor: widget.firstPointColor, lastPointColor: widget.lastPointColor, negativePointColor: widget.negativePointColor, - color: widget.color, + color: _chartThemeData.color, tiePointColor: widget.tiePointColor, borderColor: widget.borderColor, borderWidth: widget.borderWidth, @@ -594,6 +629,7 @@ class _SfSparkWinLossChartState extends State { trackball: widget.trackball, coordinatePoints: _coordinatePoints, dataPoints: _dataPoints, + themeData: _chartThemeData, ) ]))); } diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart index c10911394..9ff630c45 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/spark_chart_trackball.dart @@ -35,11 +35,7 @@ class SparkChartTrackball { this.color, this.dashArray, this.activationMode = SparkChartActivationMode.tap, - this.labelStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12), + this.labelStyle, this.tooltipFormatter, this.backgroundColor, this.shouldAlwaysShow = false, @@ -141,8 +137,7 @@ class SparkChartTrackball { /// /// Using the [TextStyle], add style data labels. /// - /// Defaults to the [TextStyle] property with font size `12.0` and - /// font family `Roboto`. + /// Defaults to null. /// /// Also refer [TextStyle]. /// @@ -158,7 +153,7 @@ class SparkChartTrackball { /// ); /// } /// ``` - final TextStyle labelStyle; + final TextStyle? labelStyle; /// Customizes the background color of the trackball tooltip. /// The color is set based on the current application theme, if its value is @@ -343,7 +338,7 @@ class SparkChartTrackball { width, color!, activationMode, - labelStyle, + labelStyle!, backgroundColor!, borderColor!, borderWidth, diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart index 000999bad..83506782d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/trackball/trackball_renderer.dart @@ -18,6 +18,7 @@ class SparkChartTrackballRenderer extends StatefulWidget { this.trackball, this.coordinatePoints, this.dataPoints, + this.themeData, this.sparkChart}) : super(key: key); @@ -30,6 +31,9 @@ class SparkChartTrackballRenderer extends StatefulWidget { /// Specifies the spark chart data points. final List? dataPoints; + /// Specifies the theme of the chart. + final SfSparkChartThemeData? themeData; + /// Specifies the spark chart widget. final Widget? sparkChart; @@ -64,7 +68,7 @@ class _SparkChartTrackballRendererState Offset? _globalPosition; /// Specifies the theme of the chart. - SfChartThemeData? _themeData; + SfSparkChartThemeData? _themeData; /// Specifies the current data point. SparkChartPoint? _currentDataPoint; @@ -93,7 +97,7 @@ class _SparkChartTrackballRendererState @override void didChangeDependencies() { - _themeData = SfChartTheme.of(context); + _themeData = widget.themeData; super.didChangeDependencies(); } @@ -291,7 +295,7 @@ class TrackballPainter extends CustomPainter { Canvas canvas, Offset? screenPoint, num index, Size size) { Offset labelOffset = screenPoint!; final String dataLabel = _getTrackballLabel(); - final TextStyle labelStyle = _getTrackballLabelStyle(); + final TextStyle labelStyle = _rendererState._themeData!.trackballTextStyle!; final Size textSize = getTextSize(dataLabel, labelStyle); final Rect areaBounds = _rendererState._areaBounds!; BorderRadius borderRadius = _trackball!.borderRadius; @@ -394,15 +398,6 @@ class TrackballPainter extends CustomPainter { return dataLabel; } - /// Method to return the trackball label style. - TextStyle _getTrackballLabelStyle() { - return _trackball!.labelStyle.copyWith( - color: _trackball!.labelStyle.color ?? - (_rendererState._themeData!.brightness == Brightness.light - ? const Color.fromRGBO(229, 229, 229, 1) - : const Color.fromRGBO(0, 0, 0, 1))); - } - /// Method to get the border radius. BorderRadius _getBorderRadius(BorderRadius borderRadius, double value) { return BorderRadius.only( @@ -432,10 +427,7 @@ class TrackballPainter extends CustomPainter { Offset screenPoint, bool isTop, bool isBottom) { - final Color backgroundColor = - _rendererState._themeData!.brightness == Brightness.light - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1); + final Color backgroundColor = _rendererState._themeData!.tooltipColor!; final Paint paint = Paint() ..color = _trackball!.backgroundColor ?? backgroundColor; final Path path = Path(); @@ -487,10 +479,8 @@ class TrackballPainter extends CustomPainter { void _drawTrackLine( Canvas canvas, Rect areaBounds, Offset screenPoint, Size size) { final Paint paint = Paint() - ..color = _trackball!.color ?? - (_rendererState._themeData!.brightness == Brightness.light - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1)) + ..color = + (_trackball!.color ?? _rendererState._themeData!.trackballLineColor)! ..strokeWidth = _trackball!.width ..style = PaintingStyle.stroke; final Offset point1 = Offset(screenPoint.dx, 0); diff --git a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart index f57a7d6ad..ef0da9f03 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart @@ -393,7 +393,7 @@ void renderMarker( num highPoint, num lowPoint, double axisCrossesAt, - SfChartThemeData themeData, + SfSparkChartThemeData themeData, Color? lowPointColor, Color? highPointColor, Color? negativePointColor, @@ -407,8 +407,7 @@ void renderMarker( final SparkChartMarkerShape markerShape = marker.shape; final double markerSize = marker.size; final SparkChartMarkerDisplayMode markerDisplayMode = marker.displayMode; - final Color themeBasedColor = - themeData.brightness == Brightness.light ? Colors.white : Colors.black; + final Color themeBasedColor = themeData.markerFillColor!; Path markerPath; final Offset lastMarkerOffset = Offset( offset.dx + @@ -575,7 +574,7 @@ void renderMarker( Color _getDataLabelSaturationColor( Offset dataLabelOffset, Offset coordinateOffset, - SfChartThemeData theme, + SfSparkChartThemeData theme, Offset offset, Color seriesColor, String type, @@ -586,25 +585,17 @@ Color _getDataLabelSaturationColor( if (type == 'Area') { dataLabelOffset.dy >= (offset.dy + coordinateOffset.dy) ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + : color = theme.dataLabelBackgroundColor!; } else if (type == 'Line') { - color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + color = theme.dataLabelBackgroundColor!; } else { yValue! > 0 ? dataLabelOffset.dy > (segment!.top + offset.dy) ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black + : color = theme.dataLabelBackgroundColor! : dataLabelOffset.dy < (segment!.top + offset.dy) ? color = seriesColor - : color = theme.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : Colors.black; + : color = theme.dataLabelBackgroundColor!; } color = getSaturationColor(color); @@ -617,14 +608,15 @@ TextStyle _getTextStyle( Offset dataLabelOffset, Offset coordinateOffset, Offset offset, - SfChartThemeData theme, + SfSparkChartThemeData theme, Color seriesColor, String type, [Rect? segment, num? yValue]) { final TextStyle font = labelStyle; - final Color fontColor = font.color ?? - _getDataLabelSaturationColor(dataLabelOffset, coordinateOffset, theme, + final Color fontColor = font.color != Colors.transparent + ? font.color! + : _getDataLabelSaturationColor(dataLabelOffset, coordinateOffset, theme, offset, seriesColor, type, segment, yValue); final TextStyle textStyle = TextStyle( @@ -663,7 +655,7 @@ void renderDataLabel( TextStyle labelStyle, SparkChartLabelDisplayMode labelDisplayMode, String type, - SfChartThemeData theme, + SfSparkChartThemeData theme, Offset offset, Color seriesColor, num highPoint, diff --git a/packages/syncfusion_flutter_charts/pubspec.yaml b/packages/syncfusion_flutter_charts/pubspec.yaml index 1407198ab..3d9283c93 100644 --- a/packages/syncfusion_flutter_charts/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_charts description: A Flutter Charts library which includes data visualization widgets such as cartesian and circular charts, to create real-time, interactive, high-performance, animated charts. -version: 23.2.6 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_charts environment: @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' vector_math: ">=2.1.0 <=3.0.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_core/CHANGELOG.md b/packages/syncfusion_flutter_core/CHANGELOG.md index 3d80ad0c4..27125cdf7 100644 --- a/packages/syncfusion_flutter_core/CHANGELOG.md +++ b/packages/syncfusion_flutter_core/CHANGELOG.md @@ -1,5 +1,10 @@ ## Unreleased +**General** +* Provided th​e Material 3 themes support. + +## [20.2.38] - 07/12/2022 + **Features** * The [Flutter PDF Library](https://www.syncfusion.com/document-processing/pdf-framework/flutter/pdf-library) has been developed to meet industry standards and is now marked as a production-ready. diff --git a/packages/syncfusion_flutter_core/lib/analysis_options.yaml b/packages/syncfusion_flutter_core/lib/analysis_options.yaml index 8632e215b..d123c852a 100644 --- a/packages/syncfusion_flutter_core/lib/analysis_options.yaml +++ b/packages/syncfusion_flutter_core/lib/analysis_options.yaml @@ -71,8 +71,6 @@ linter: # https://github.com/dart-lang/linter/blob/master/example/all.yaml - always_declare_return_types - always_put_control_body_on_new_line - # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 - - always_require_non_null_named_parameters - always_specify_types # - always_use_package_imports # we do this commonly - annotate_overrides @@ -101,8 +99,6 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - # - avoid_returning_null # still violated by some pre-nnbd code that we haven't yet migrated - - avoid_returning_null_for_future - avoid_returning_null_for_void # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives - avoid_setters_without_getters diff --git a/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart b/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart index 323e73325..9a189e8fa 100644 --- a/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart +++ b/packages/syncfusion_flutter_core/lib/src/calendar/hijri_date_time.dart @@ -1958,7 +1958,7 @@ class HijriDateTime { } @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart index f37268313..9e9fee74b 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/barcodes_theme.dart @@ -127,7 +127,20 @@ class SfBarcodeTheme extends InheritedTheme { @immutable class SfBarcodeThemeData with Diagnosticable { /// Initialize the SfBarcode theme data - factory SfBarcodeThemeData({ + const SfBarcodeThemeData({ + this.backgroundColor, + this.barColor, + this.textColor, + this.textStyle, + }); + + /// Create a [SfBarcodeThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfBarcodeThemeData] constructor. + factory SfBarcodeThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? barColor, @@ -140,8 +153,7 @@ class SfBarcodeThemeData with Diagnosticable { barColor ??= isLight ? const Color(0xFF212121) : const Color(0xFFE0E0E0); textColor ??= isLight ? const Color(0xFF212121) : const Color(0xFFE0E0E0); - return SfBarcodeThemeData.raw( - brightness: brightness, + return SfBarcodeThemeData( backgroundColor: backgroundColor, barColor: barColor, textColor: textColor, @@ -149,53 +161,6 @@ class SfBarcodeThemeData with Diagnosticable { ); } - /// Create a [SfBarcodeThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfBarcodeThemeData] constructor. - const SfBarcodeThemeData.raw({ - required this.brightness, - required this.backgroundColor, - required this.barColor, - required this.textColor, - required this.textStyle, - }); - - /// The brightness of the overall theme of the - /// application for the barcode widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// barcode widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// barcodeThemeData: SfBarcodeThemeData( - /// brightness: Brightness.dark - /// ), - /// ), - /// child: SfBarcodeGenerator( - /// value: 'www.sycfusion.com', - /// symbology: QRCode() , - /// showValue: true, - /// ), - /// ), - /// ) - /// ); - ///} - /// ``` - final Brightness brightness; - /// Specifies the background color of barcode widgets. /// /// ```dart @@ -219,7 +184,7 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Color backgroundColor; + final Color? backgroundColor; /// Specifies the color for barcodes. /// @@ -244,7 +209,7 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Color barColor; + final Color? barColor; /// Specifies the color for barcode text. /// @@ -269,7 +234,7 @@ class SfBarcodeThemeData with Diagnosticable { /// ); ///} /// ``` - final Color textColor; + final Color? textColor; /// Specifies the text style for barcode text. /// @@ -306,7 +271,7 @@ class SfBarcodeThemeData with Diagnosticable { TextStyle? textStyle, }) { return SfBarcodeThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, barColor: barColor ?? this.barColor, textColor: textColor ?? this.textColor, @@ -356,9 +321,7 @@ class SfBarcodeThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfBarcodeThemeData defaultData = SfBarcodeThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfBarcodeThemeData defaultData = SfBarcodeThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('barColor', barColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart index a5715a0fa..4dbabc7d5 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart @@ -102,9 +102,44 @@ class SfCalendarTheme extends InheritedTheme { /// ``` @immutable class SfCalendarThemeData with Diagnosticable { + /// Create a [SfCalendarThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfCalendarThemeData] constructor. + const SfCalendarThemeData( + {this.backgroundColor, + this.headerTextStyle, + this.headerBackgroundColor, + this.agendaBackgroundColor, + this.cellBorderColor, + this.viewHeaderDateTextStyle, + this.viewHeaderDayTextStyle, + this.viewHeaderBackgroundColor, + this.agendaDayTextStyle, + this.agendaDateTextStyle, + this.timeTextStyle, + this.activeDatesTextStyle, + this.activeDatesBackgroundColor, + this.todayBackgroundColor, + this.trailingDatesBackgroundColor, + this.leadingDatesBackgroundColor, + this.trailingDatesTextStyle, + this.blackoutDatesTextStyle, + this.displayNameTextStyle, + this.leadingDatesTextStyle, + this.todayTextStyle, + this.todayHighlightColor, + this.weekNumberBackgroundColor, + this.selectionBorderColor, + this.weekNumberTextStyle, + this.timeIndicatorTextStyle, + this.allDayPanelColor}); + /// Create a [SfCalendarThemeData] that's used to configure a /// [SfCalendarTheme]. - factory SfCalendarThemeData({ + factory SfCalendarThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? headerBackgroundColor, @@ -134,8 +169,8 @@ class SfCalendarThemeData with Diagnosticable { TextStyle? weekNumberTextStyle, TextStyle? timeIndicatorTextStyle, }) { - return SfCalendarThemeData.raw( - brightness: brightness, + brightness = brightness ?? Brightness.light; + return SfCalendarThemeData( backgroundColor: backgroundColor, headerTextStyle: headerTextStyle, headerBackgroundColor: headerBackgroundColor, @@ -165,70 +200,6 @@ class SfCalendarThemeData with Diagnosticable { timeIndicatorTextStyle: timeIndicatorTextStyle); } - /// Create a [SfCalendarThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfCalendarThemeData] constructor. - const SfCalendarThemeData.raw( - {required this.brightness, - required this.backgroundColor, - required this.headerTextStyle, - required this.headerBackgroundColor, - required this.agendaBackgroundColor, - required this.cellBorderColor, - required this.viewHeaderDateTextStyle, - required this.viewHeaderDayTextStyle, - required this.viewHeaderBackgroundColor, - required this.agendaDayTextStyle, - required this.agendaDateTextStyle, - required this.timeTextStyle, - required this.activeDatesTextStyle, - required this.activeDatesBackgroundColor, - required this.todayBackgroundColor, - required this.trailingDatesBackgroundColor, - required this.leadingDatesBackgroundColor, - required this.trailingDatesTextStyle, - required this.blackoutDatesTextStyle, - required this.displayNameTextStyle, - required this.leadingDatesTextStyle, - required this.todayTextStyle, - required this.todayHighlightColor, - required this.weekNumberBackgroundColor, - required this.selectionBorderColor, - required this.weekNumberTextStyle, - required this.timeIndicatorTextStyle, - required this.allDayPanelColor}); - - /// The brightness of the overall theme of the - /// application for the calendar widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// calendar widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// calendarThemeData: SfCalendarThemeData( - /// brightness: Brightness.light - /// ) - /// ), - /// child: SfCalendar(), - /// ), - /// ) - /// ); - ///} - /// ``` - final Brightness? brightness; - /// Specifies the background color of calendar widgets. /// /// ```dart @@ -844,7 +815,7 @@ class SfCalendarThemeData with Diagnosticable { Color? selectionBorderColor, Color? allDayPanelColor}) { return SfCalendarThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, headerTextStyle: headerTextStyle ?? this.headerTextStyle, headerBackgroundColor: @@ -994,9 +965,7 @@ class SfCalendarThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfCalendarThemeData defaultData = SfCalendarThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfCalendarThemeData defaultData = SfCalendarThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('headerBackgroundColor', headerBackgroundColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart index 8cdcd35d1..5dbcde093 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/charts_theme.dart @@ -119,7 +119,54 @@ class SfChartTheme extends InheritedTheme { @immutable class SfChartThemeData with Diagnosticable { /// Creating an argument constructor of SfChartThemeData class. - factory SfChartThemeData({ + const SfChartThemeData({ + this.axisLabelColor, + this.axisLineColor, + this.axisTitleColor, + this.backgroundColor, + this.titleTextColor, + this.crosshairBackgroundColor, + this.crosshairLabelColor, + this.crosshairLineColor, + this.legendBackgroundColor, + this.legendTextColor, + this.legendTitleColor, + this.majorGridLineColor, + this.majorTickLineColor, + this.minorGridLineColor, + this.minorTickLineColor, + this.plotAreaBackgroundColor, + this.plotAreaBorderColor, + this.selectionRectColor, + this.selectionRectBorderColor, + this.selectionTooltipConnectorLineColor, + this.titleBackgroundColor, + this.tooltipColor, + this.tooltipSeparatorColor, + this.tooltipLabelColor, + this.waterfallConnectorLineColor, + this.titleTextStyle, + this.axisTitleTextStyle, + this.axisLabelTextStyle, + this.axisMultiLevelLabelTextStyle, + this.plotBandLabelTextStyle, + this.legendTitleTextStyle, + this.legendTextStyle, + this.dataLabelTextStyle, + this.tooltipTextStyle, + this.trackballTextStyle, + this.crosshairTextStyle, + this.selectionZoomingTooltipTextStyle, + }); + + /// Create a [SfChartThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfChartThemeData] constructor. + /// + factory SfChartThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? axisLabelColor, @@ -160,77 +207,8 @@ class SfChartThemeData with Diagnosticable { TextStyle? selectionZoomingTooltipTextStyle, }) { brightness = brightness ?? Brightness.light; - final bool isLight = brightness == Brightness.light; - backgroundColor ??= Colors.transparent; - axisLabelColor ??= isLight - ? const Color.fromRGBO(104, 104, 104, 1) - : const Color.fromRGBO(242, 242, 242, 1); - axisTitleColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - axisLineColor ??= isLight - ? const Color.fromRGBO(181, 181, 181, 1) - : const Color.fromRGBO(101, 101, 101, 1); - majorGridLineColor ??= isLight - ? const Color.fromRGBO(219, 219, 219, 1) - : const Color.fromRGBO(70, 74, 86, 1); - minorGridLineColor ??= isLight - ? const Color.fromRGBO(234, 234, 234, 1) - : const Color.fromRGBO(70, 74, 86, 1); - majorTickLineColor ??= isLight - ? const Color.fromRGBO(181, 181, 181, 1) - : const Color.fromRGBO(191, 191, 191, 1); - minorTickLineColor ??= isLight - ? const Color.fromRGBO(214, 214, 214, 1) - : const Color.fromRGBO(150, 150, 150, 1); - titleTextColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - titleBackgroundColor ??= Colors.transparent; - legendTextColor ??= isLight - ? const Color.fromRGBO(53, 53, 53, 1) - : const Color.fromRGBO(255, 255, 255, 1); - legendBackgroundColor ??= Colors.transparent; - legendTitleColor ??= isLight - ? const Color.fromRGBO(66, 66, 66, 1) - : const Color.fromRGBO(255, 255, 255, 1); - plotAreaBackgroundColor ??= Colors.transparent; - plotAreaBorderColor ??= isLight - ? const Color.fromRGBO(219, 219, 219, 1) - : const Color.fromRGBO(101, 101, 101, 1); - crosshairLineColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1); - crosshairBackgroundColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(255, 255, 255, 1); - crosshairLabelColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(0, 0, 0, 1); - tooltipColor ??= isLight - ? const Color.fromRGBO(0, 8, 22, 1) - : const Color.fromRGBO(255, 255, 255, 1); - tooltipLabelColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(0, 0, 0, 1); - tooltipSeparatorColor ??= isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(150, 150, 150, 1); - selectionRectColor ??= isLight - ? const Color.fromRGBO(41, 171, 226, 0.1) - : const Color.fromRGBO(255, 217, 57, 0.3); - selectionRectBorderColor ??= isLight - ? const Color.fromRGBO(41, 171, 226, 1) - : const Color.fromRGBO(255, 255, 255, 1); - selectionTooltipConnectorLineColor ??= isLight - ? const Color.fromRGBO(79, 79, 79, 1) - : const Color.fromRGBO(150, 150, 150, 1); - waterfallConnectorLineColor ??= isLight - ? const Color.fromRGBO(0, 0, 0, 1) - : const Color.fromRGBO(255, 255, 255, 1); - return SfChartThemeData.raw( - brightness: brightness, + return SfChartThemeData( axisLabelColor: axisLabelColor, axisLineColor: axisLineColor, axisTitleColor: axisTitleColor, @@ -271,81 +249,6 @@ class SfChartThemeData with Diagnosticable { ); } - /// Create a [SfChartThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfChartThemeData] constructor. - /// - const SfChartThemeData.raw({ - required this.brightness, - required this.axisLabelColor, - required this.axisLineColor, - required this.axisTitleColor, - required this.backgroundColor, - required this.titleTextColor, - required this.crosshairBackgroundColor, - required this.crosshairLabelColor, - required this.crosshairLineColor, - required this.legendBackgroundColor, - required this.legendTextColor, - required this.legendTitleColor, - required this.majorGridLineColor, - required this.majorTickLineColor, - required this.minorGridLineColor, - required this.minorTickLineColor, - required this.plotAreaBackgroundColor, - required this.plotAreaBorderColor, - required this.selectionRectColor, - required this.selectionRectBorderColor, - required this.selectionTooltipConnectorLineColor, - required this.titleBackgroundColor, - required this.tooltipColor, - required this.tooltipSeparatorColor, - required this.tooltipLabelColor, - required this.waterfallConnectorLineColor, - required this.titleTextStyle, - required this.axisTitleTextStyle, - required this.axisLabelTextStyle, - required this.axisMultiLevelLabelTextStyle, - required this.plotBandLabelTextStyle, - required this.legendTitleTextStyle, - required this.legendTextStyle, - required this.dataLabelTextStyle, - required this.tooltipTextStyle, - required this.trackballTextStyle, - required this.crosshairTextStyle, - required this.selectionZoomingTooltipTextStyle, - }); - - /// The brightness of the overall theme of the - /// application for the chart widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// chart widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// chartThemeData: SfChartThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfCartesianChart() - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness brightness; - /// Specifies the background color of chart widgets. /// /// ```dart @@ -364,7 +267,7 @@ class SfChartThemeData with Diagnosticable { /// ); /// } ///``` - final Color backgroundColor; + final Color? backgroundColor; /// Specifies the color for axis labels. /// @@ -385,7 +288,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisLabelColor; + final Color? axisLabelColor; /// Specifies the color for axis title. /// @@ -410,7 +313,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisTitleColor; + final Color? axisTitleColor; /// Specifies the color for axis line. /// @@ -431,7 +334,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color axisLineColor; + final Color? axisLineColor; /// Specifies the color for major grid line of an axis. /// @@ -452,7 +355,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color majorGridLineColor; + final Color? majorGridLineColor; /// Specifies the color for minor grid line of an axis. /// @@ -475,7 +378,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color minorGridLineColor; + final Color? minorGridLineColor; /// Specifies the color for major tick line of an axis. /// @@ -496,7 +399,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color majorTickLineColor; + final Color? majorTickLineColor; /// Specifies the color for minor tick line of an axis. /// @@ -519,7 +422,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color minorTickLineColor; + final Color? minorTickLineColor; /// Specifies the color of the chart title. /// @@ -541,7 +444,7 @@ class SfChartThemeData with Diagnosticable { /// ); /// } ///``` - final Color titleTextColor; + final Color? titleTextColor; /// Specifies the background color for title of the chart. /// @@ -564,7 +467,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color titleBackgroundColor; + final Color? titleBackgroundColor; /// Specifies the text color of the legend. /// @@ -594,7 +497,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendTextColor; + final Color? legendTextColor; /// Specifies the title color of the legend. /// @@ -625,7 +528,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendTitleColor; + final Color? legendTitleColor; /// Specifies the background color of the legend. /// @@ -655,7 +558,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color legendBackgroundColor; + final Color? legendBackgroundColor; /// Specifies the background color of the plot area of the chart. /// @@ -676,7 +579,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color plotAreaBackgroundColor; + final Color? plotAreaBackgroundColor; /// Specifies the border color of the plot area of the chart. /// @@ -697,7 +600,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color plotAreaBorderColor; + final Color? plotAreaBorderColor; /// Specifies the crosshair line color. /// @@ -728,7 +631,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairLineColor; + final Color? crosshairLineColor; /// Specifies the background color of the crosshair. /// @@ -759,7 +662,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairBackgroundColor; + final Color? crosshairBackgroundColor; /// Specifies the color of the crosshair text. /// @@ -790,7 +693,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color crosshairLabelColor; + final Color? crosshairLabelColor; /// Specifies the color of the tooltip. /// @@ -820,7 +723,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipColor; + final Color? tooltipColor; /// Specifies the text color of the tooltip. /// @@ -850,7 +753,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipLabelColor; + final Color? tooltipLabelColor; /// Specifies the line color of the tooltip /// which separates the header and values. @@ -881,7 +784,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color tooltipSeparatorColor; + final Color? tooltipSeparatorColor; /// Specifies the color of an rectangle which is used for selection zooming. /// @@ -913,7 +816,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionRectColor; + final Color? selectionRectColor; /// Specifies the stroke color of an rectangle /// which is used for selection zooming. @@ -946,7 +849,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionRectBorderColor; + final Color? selectionRectBorderColor; /// Specifies the connector line color which is used in selection zooming. /// @@ -978,7 +881,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color selectionTooltipConnectorLineColor; + final Color? selectionTooltipConnectorLineColor; /// Specifies the connector line color for the waterfall chart. /// @@ -1007,7 +910,7 @@ class SfChartThemeData with Diagnosticable { /// } ///``` - final Color waterfallConnectorLineColor; + final Color? waterfallConnectorLineColor; /// Specifies the text style for title. /// @@ -1305,7 +1208,7 @@ class SfChartThemeData with Diagnosticable { TextStyle? selectionZoomingTooltipTextStyle, }) { return SfChartThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, axisLabelColor: axisLabelColor ?? this.axisLabelColor, axisLineColor: axisLineColor ?? this.axisLineColor, axisTitleColor: axisTitleColor ?? this.axisTitleColor, @@ -1532,9 +1435,7 @@ class SfChartThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfChartThemeData defaultData = SfChartThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfChartThemeData defaultData = SfChartThemeData(); properties.add(ColorProperty('axisLabelColor', axisLabelColor, defaultValue: defaultData.axisLabelColor)); properties.add(ColorProperty('axisLineColor', axisLineColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart index 01f6446e9..c7c3165da 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart @@ -107,7 +107,38 @@ class SfDataGridTheme extends InheritedTheme { class SfDataGridThemeData with Diagnosticable { /// Create a [SfDataGridThemeData] that's used to configure a /// [SfDataGridTheme]. - factory SfDataGridThemeData({ + const SfDataGridThemeData( + {this.gridLineColor, + this.gridLineStrokeWidth, + this.selectionColor, + this.currentCellStyle, + this.frozenPaneLineColor, + this.frozenPaneLineWidth, + this.sortIconColor, + this.headerColor, + this.headerHoverColor, + this.frozenPaneElevation, + this.columnResizeIndicatorColor, + this.columnResizeIndicatorStrokeWidth, + this.rowHoverColor, + this.rowHoverTextStyle, + this.sortIcon, + this.filterIcon, + this.filterIconColor, + this.filterIconHoverColor, + this.sortOrderNumberColor, + this.sortOrderNumberBackgroundColor, + this.filterPopupTextStyle, + this.filterPopupDisabledTextStyle, + this.columnDragIndicatorColor, + this.columnDragIndicatorStrokeWidth, + this.groupExpanderIcon, + this.indentColumnWidth, + this.indentColumnColor}); + + /// Create a [SfDataGridThemeData] that's used to configure a + /// [SfDataGridTheme]. + factory SfDataGridThemeData.raw({ Brightness? brightness, Color? gridLineColor, double? gridLineStrokeWidth, @@ -134,11 +165,11 @@ class SfDataGridThemeData with Diagnosticable { Color? columnDragIndicatorColor, double? columnDragIndicatorStrokeWidth, Widget? groupExpanderIcon, - double indentColumnWidth = 40.0, + double? indentColumnWidth, Color? indentColumnColor, }) { - return SfDataGridThemeData.raw( - brightness: brightness, + brightness = brightness ?? Brightness.light; + return SfDataGridThemeData( gridLineColor: gridLineColor, gridLineStrokeWidth: gridLineStrokeWidth, selectionColor: selectionColor, @@ -168,70 +199,6 @@ class SfDataGridThemeData with Diagnosticable { indentColumnColor: indentColumnColor); } - /// Create a [SfDataGridThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfDataGridThemeData] constructor. - /// - const SfDataGridThemeData.raw( - {required this.brightness, - required this.gridLineColor, - required this.gridLineStrokeWidth, - required this.selectionColor, - required this.currentCellStyle, - required this.frozenPaneLineColor, - required this.frozenPaneLineWidth, - required this.sortIconColor, - required this.headerColor, - required this.headerHoverColor, - required this.frozenPaneElevation, - required this.columnResizeIndicatorColor, - required this.columnResizeIndicatorStrokeWidth, - required this.rowHoverColor, - required this.rowHoverTextStyle, - required this.sortIcon, - required this.filterIcon, - required this.filterIconColor, - required this.filterIconHoverColor, - required this.sortOrderNumberColor, - required this.sortOrderNumberBackgroundColor, - required this.filterPopupTextStyle, - required this.filterPopupDisabledTextStyle, - required this.columnDragIndicatorColor, - required this.columnDragIndicatorStrokeWidth, - required this.groupExpanderIcon, - required this.indentColumnWidth, - required this.indentColumnColor}); - - /// The brightness of the overall theme of the - /// application for the [SfDataGrid] widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).colorScheme.brightness], brightness for - /// datagrid widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dataGridThemeData: SfDataGridThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfDataGrid(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness? brightness; - /// The color for grid line. /// /// ```dart @@ -526,7 +493,7 @@ class SfDataGridThemeData with Diagnosticable { /// The width of an indent column. /// /// Defaults to 40.0. - final double indentColumnWidth; + final double? indentColumnWidth; /// The color of an indent column. final Color? indentColumnColor; @@ -564,7 +531,7 @@ class SfDataGridThemeData with Diagnosticable { Color? indentColumnColor, }) { return SfDataGridThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, gridLineColor: gridLineColor ?? this.gridLineColor, gridLineStrokeWidth: gridLineStrokeWidth ?? this.gridLineStrokeWidth, selectionColor: selectionColor ?? this.selectionColor, @@ -665,7 +632,6 @@ class SfDataGridThemeData with Diagnosticable { } return other is SfDataGridThemeData && - other.brightness == brightness && other.gridLineColor == gridLineColor && other.gridLineStrokeWidth == gridLineStrokeWidth && other.selectionColor == selectionColor && @@ -735,9 +701,7 @@ class SfDataGridThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDataGridThemeData defaultData = SfDataGridThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfDataGridThemeData defaultData = SfDataGridThemeData(); properties.add(ColorProperty('gridLineColor', gridLineColor, defaultValue: defaultData.gridLineColor)); properties.add(DoubleProperty('gridLineStrokeWidth', gridLineStrokeWidth, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart index 2095badd1..ddc4a28f8 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datapager_theme.dart @@ -51,7 +51,22 @@ class SfDataPagerTheme extends InheritedTheme { class SfDataPagerThemeData with Diagnosticable { /// Create a [SfDataPagerThemeData] that's used to configure a /// [SfDataPagerTheme]. - factory SfDataPagerThemeData( + const SfDataPagerThemeData( + {this.backgroundColor, + this.itemColor, + this.itemTextStyle, + this.selectedItemColor, + this.selectedItemTextStyle, + this.disabledItemColor, + this.disabledItemTextStyle, + this.itemBorderColor, + this.itemBorderWidth, + this.itemBorderRadius, + this.dropdownButtonBorderColor}); + + /// Create a [SfDataPagerThemeData] that's used to configure a + /// [SfDataPagerTheme]. + factory SfDataPagerThemeData.raw( {Brightness? brightness, Color? backgroundColor, Color? itemColor, @@ -64,8 +79,8 @@ class SfDataPagerThemeData with Diagnosticable { double? itemBorderWidth, BorderRadiusGeometry? itemBorderRadius, Color? dropdownButtonBorderColor}) { - return SfDataPagerThemeData.raw( - brightness: brightness, + brightness = brightness ?? Brightness.light; + return SfDataPagerThemeData( backgroundColor: backgroundColor, itemColor: itemColor, itemTextStyle: itemTextStyle, @@ -79,30 +94,6 @@ class SfDataPagerThemeData with Diagnosticable { dropdownButtonBorderColor: dropdownButtonBorderColor); } - /// Create a [SfDataPagerThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfDataPagerThemeData] constructor. - const SfDataPagerThemeData.raw( - {required this.brightness, - required this.backgroundColor, - required this.itemColor, - required this.itemTextStyle, - required this.selectedItemColor, - required this.selectedItemTextStyle, - required this.disabledItemColor, - required this.disabledItemTextStyle, - required this.itemBorderColor, - required this.itemBorderWidth, - required this.itemBorderRadius, - required this.dropdownButtonBorderColor}); - - /// The brightness of the overall theme of the - /// application for the [SfDataPager] widgets. - final Brightness? brightness; - /// The color of the page Items final Color? itemColor; @@ -158,7 +149,7 @@ class SfDataPagerThemeData with Diagnosticable { BorderRadiusGeometry? itemBorderRadius, Color? dropdownButtonBorderColor}) { return SfDataPagerThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, itemColor: itemColor ?? this.itemColor, itemTextStyle: itemTextStyle ?? this.itemTextStyle, @@ -211,7 +202,6 @@ class SfDataPagerThemeData with Diagnosticable { } return other is SfDataPagerThemeData && - other.brightness == brightness && other.itemColor == itemColor && other.backgroundColor == backgroundColor && other.itemTextStyle == itemTextStyle && @@ -246,9 +236,7 @@ class SfDataPagerThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDataPagerThemeData defaultData = SfDataPagerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfDataPagerThemeData defaultData = SfDataPagerThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('itemColor', itemColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart index 9dee7199d..9bcf9a4a3 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/daterangepicker_theme.dart @@ -105,9 +105,43 @@ class SfDateRangePickerTheme extends InheritedTheme { /// ``` @immutable class SfDateRangePickerThemeData with Diagnosticable { + /// Create a [SfDateRangePickerThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfDateRangePickerThemeData] constructor. + const SfDateRangePickerThemeData( + {this.backgroundColor, + this.viewHeaderTextStyle, + this.headerTextStyle, + this.trailingDatesTextStyle, + this.leadingCellTextStyle, + this.activeDatesTextStyle, + this.cellTextStyle, + this.rangeSelectionTextStyle, + this.rangeSelectionColor, + this.leadingDatesTextStyle, + this.disabledDatesTextStyle, + this.disabledCellTextStyle, + this.selectionColor, + this.selectionTextStyle, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.headerBackgroundColor, + this.viewHeaderBackgroundColor, + this.weekNumberBackgroundColor, + this.blackoutDatesTextStyle, + this.todayHighlightColor, + this.todayTextStyle, + this.todayCellTextStyle, + this.weekendDatesTextStyle, + this.specialDatesTextStyle, + this.weekNumberTextStyle}); + /// Create a [SfDateRangePickerThemeData] that's used to configure a /// [SfDateRangePickerTheme]. - factory SfDateRangePickerThemeData({ + factory SfDateRangePickerThemeData.raw({ Brightness? brightness, Color? backgroundColor, Color? startRangeSelectionColor, @@ -136,8 +170,8 @@ class SfDateRangePickerThemeData with Diagnosticable { TextStyle? specialDatesTextStyle, TextStyle? weekNumberTextStyle, }) { - return SfDateRangePickerThemeData.raw( - brightness: brightness, + brightness = brightness ?? Brightness.light; + return SfDateRangePickerThemeData( backgroundColor: backgroundColor, viewHeaderTextStyle: viewHeaderTextStyle, headerTextStyle: headerTextStyle, @@ -166,69 +200,6 @@ class SfDateRangePickerThemeData with Diagnosticable { weekNumberTextStyle: weekNumberTextStyle); } - /// Create a [SfDateRangePickerThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfDateRangePickerThemeData] constructor. - const SfDateRangePickerThemeData.raw( - {required this.brightness, - required this.backgroundColor, - required this.viewHeaderTextStyle, - required this.headerTextStyle, - required this.trailingDatesTextStyle, - required this.leadingCellTextStyle, - required this.activeDatesTextStyle, - required this.cellTextStyle, - required this.rangeSelectionTextStyle, - required this.rangeSelectionColor, - required this.leadingDatesTextStyle, - required this.disabledDatesTextStyle, - required this.disabledCellTextStyle, - required this.selectionColor, - required this.selectionTextStyle, - required this.startRangeSelectionColor, - required this.endRangeSelectionColor, - required this.headerBackgroundColor, - required this.viewHeaderBackgroundColor, - required this.weekNumberBackgroundColor, - required this.blackoutDatesTextStyle, - required this.todayHighlightColor, - required this.todayTextStyle, - required this.todayCellTextStyle, - required this.weekendDatesTextStyle, - required this.specialDatesTextStyle, - required this.weekNumberTextStyle}); - - /// The brightness of the overall theme of the - /// application for the date picker widget. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// date range picker widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// dateRangePickerThemeData: SfDateRangePickerThemeData( - /// brightness: Brightness.light - /// ) - /// ), - /// child: SfDateRangePicker(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness? brightness; - /// Specifies the background color of date picker widget. /// /// ```dart @@ -821,7 +792,7 @@ class SfDateRangePickerThemeData with Diagnosticable { TextStyle? weekNumberTextStyle, }) { return SfDateRangePickerThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, viewHeaderTextStyle: viewHeaderTextStyle ?? this.viewHeaderTextStyle, headerTextStyle: headerTextStyle ?? this.headerTextStyle, @@ -964,9 +935,7 @@ class SfDateRangePickerThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfDateRangePickerThemeData defaultData = SfDateRangePickerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfDateRangePickerThemeData defaultData = SfDateRangePickerThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('rangeSelectionColor', rangeSelectionColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart index 7501de3f8..780e83705 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/gauges_theme.dart @@ -80,7 +80,6 @@ class SfGaugeTheme extends InheritedTheme { @override bool updateShouldNotify(SfGaugeTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final SfGaugeTheme? ancestorTheme = @@ -119,7 +118,35 @@ class SfGaugeTheme extends InheritedTheme { @immutable class SfGaugeThemeData with Diagnosticable { /// Initialize the gauge theme data - factory SfGaugeThemeData({ + const SfGaugeThemeData( + {this.backgroundColor = Colors.transparent, + this.titleColor, + this.axisLabelColor, + this.axisLineColor, + this.majorTickColor, + this.minorTickColor, + this.markerColor, + this.markerBorderColor = Colors.transparent, + this.needleColor, + this.knobColor, + this.knobBorderColor = Colors.transparent, + this.tailColor, + this.tailBorderColor = Colors.transparent, + this.rangePointerColor, + this.rangeColor, + this.titleBorderColor = Colors.transparent, + this.titleBackgroundColor = Colors.transparent, + this.titleTextStyle, + this.axisLabelTextStyle, + this.markerTextStyle}); + + /// Create a [SfGaugeThemeData] given a set of exact values. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfGaugeThemeData] + /// + factory SfGaugeThemeData.raw({ Brightness? brightness, Color backgroundColor = Colors.transparent, Color? titleColor, @@ -142,8 +169,8 @@ class SfGaugeThemeData with Diagnosticable { TextStyle? axisLabelTextStyle, TextStyle? markerTextStyle, }) { - return SfGaugeThemeData.raw( - brightness: brightness, + brightness = brightness ?? Brightness.light; + return SfGaugeThemeData( backgroundColor: backgroundColor, titleColor: titleColor, axisLabelColor: axisLabelColor, @@ -173,56 +200,6 @@ class SfGaugeThemeData with Diagnosticable { /// create intermediate themes based on two themes created with the /// [SfGaugeThemeData] constructor. /// - const SfGaugeThemeData.raw( - {required this.brightness, - required this.backgroundColor, - required this.titleColor, - required this.axisLabelColor, - required this.axisLineColor, - required this.majorTickColor, - required this.minorTickColor, - required this.markerColor, - required this.markerBorderColor, - required this.needleColor, - required this.knobColor, - required this.knobBorderColor, - required this.tailColor, - required this.tailBorderColor, - required this.rangePointerColor, - required this.rangeColor, - required this.titleBorderColor, - required this.titleBackgroundColor, - required this.titleTextStyle, - required this.axisLabelTextStyle, - required this.markerTextStyle}); - - /// The brightness of the overall theme of the - /// application for the gauge widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// radial gauge widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// gaugeThemeData: SfGaugeThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfRadialGauge(), - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness? brightness; /// Specifies the background color of gauge widgets. /// @@ -807,7 +784,7 @@ class SfGaugeThemeData with Diagnosticable { TextStyle? axisLabelTextStyle, TextStyle? markerTextStyle}) { return SfGaugeThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, backgroundColor: backgroundColor ?? this.backgroundColor, titleColor: titleColor ?? this.titleColor, axisLabelColor: axisLabelColor ?? this.axisLabelColor, @@ -872,7 +849,6 @@ class SfGaugeThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - return other is SfGaugeThemeData && other.backgroundColor == backgroundColor && other.titleColor == titleColor && @@ -926,9 +902,7 @@ class SfGaugeThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfGaugeThemeData defaultData = SfGaugeThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfGaugeThemeData defaultData = SfGaugeThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('titleColor', titleColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart index 14e48148f..398532110 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/maps_theme.dart @@ -127,10 +127,46 @@ class SfMapsTheme extends InheritedTheme { /// ``` @immutable class SfMapsThemeData with Diagnosticable { + /// Create a [SfMapsThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfMapsThemeData] constructor. + const SfMapsThemeData({ + this.layerColor, + this.layerStrokeColor, + this.layerStrokeWidth = 1.0, + this.shapeHoverColor, + this.shapeHoverStrokeColor, + this.shapeHoverStrokeWidth, + this.legendTextStyle, + this.markerIconColor, + this.markerIconStrokeColor, + this.markerIconStrokeWidth = 1.0, + this.dataLabelTextStyle, + this.bubbleColor, + this.bubbleStrokeColor, + this.bubbleStrokeWidth = 1.0, + this.bubbleHoverColor, + this.bubbleHoverStrokeColor, + this.bubbleHoverStrokeWidth, + this.selectionColor, + this.selectionStrokeColor, + this.selectionStrokeWidth = 0.5, + this.tooltipColor, + this.tooltipStrokeColor, + this.tooltipStrokeWidth = 1.0, + this.tooltipBorderRadius = const BorderRadius.all(Radius.circular(4.0)), + this.toggledItemColor, + this.toggledItemStrokeColor, + this.toggledItemStrokeWidth, + }); + /// Returns a new instance of [SfMapsThemeData.raw] for the given values. /// /// If any of the values are null, the default values will be set. - factory SfMapsThemeData({ + factory SfMapsThemeData.raw({ Brightness? brightness, Color? layerColor, Color? layerStrokeColor, @@ -168,8 +204,7 @@ class SfMapsThemeData with Diagnosticable { tooltipStrokeWidth ??= 1.0; tooltipBorderRadius ??= const BorderRadius.all(Radius.circular(4.0)); - return SfMapsThemeData.raw( - brightness: brightness, + return SfMapsThemeData( layerColor: layerColor, layerStrokeColor: layerStrokeColor, shapeHoverColor: shapeHoverColor, @@ -200,70 +235,6 @@ class SfMapsThemeData with Diagnosticable { ); } - /// Create a [SfMapsThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfMapsThemeData] constructor. - const SfMapsThemeData.raw({ - required this.brightness, - required this.layerColor, - required this.layerStrokeColor, - required this.layerStrokeWidth, - required this.shapeHoverColor, - required this.shapeHoverStrokeColor, - required this.shapeHoverStrokeWidth, - required this.legendTextStyle, - required this.markerIconColor, - required this.markerIconStrokeColor, - required this.markerIconStrokeWidth, - required this.dataLabelTextStyle, - required this.bubbleColor, - required this.bubbleStrokeColor, - required this.bubbleStrokeWidth, - required this.bubbleHoverColor, - required this.bubbleHoverStrokeColor, - required this.bubbleHoverStrokeWidth, - required this.selectionColor, - required this.selectionStrokeColor, - required this.selectionStrokeWidth, - required this.tooltipColor, - required this.tooltipStrokeColor, - required this.tooltipStrokeWidth, - required this.tooltipBorderRadius, - required this.toggledItemColor, - required this.toggledItemStrokeColor, - required this.toggledItemStrokeWidth, - }); - - /// The brightness of the overall theme of the - /// application for the maps widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// maps widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// mapsThemeData: SfMapsThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfMaps(), - /// ), - /// ) - /// ); - /// } - /// ``` - final Brightness brightness; - /// Specifies the fill color for maps layer. /// /// ```dart @@ -888,7 +859,7 @@ class SfMapsThemeData with Diagnosticable { double? toggledItemStrokeWidth, }) { return SfMapsThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, layerColor: layerColor ?? this.layerColor, layerStrokeColor: layerStrokeColor ?? this.layerStrokeColor, layerStrokeWidth: layerStrokeWidth ?? this.layerStrokeWidth, @@ -938,7 +909,7 @@ class SfMapsThemeData with Diagnosticable { return SfMapsThemeData( layerColor: Color.lerp(a!.layerColor, b!.layerColor, t), layerStrokeColor: Color.lerp(a.layerStrokeColor, b.layerStrokeColor, t), - layerStrokeWidth: lerpDouble(a.layerStrokeWidth, b.layerStrokeWidth, t), + layerStrokeWidth: lerpDouble(a.layerStrokeWidth, b.layerStrokeWidth, t)!, shapeHoverColor: Color.lerp(a.shapeHoverColor, b.shapeHoverColor, t), shapeHoverStrokeColor: Color.lerp(a.shapeHoverStrokeColor, b.shapeHoverStrokeColor, t), @@ -949,14 +920,14 @@ class SfMapsThemeData with Diagnosticable { markerIconStrokeColor: Color.lerp(a.markerIconStrokeColor, b.markerIconStrokeColor, t), markerIconStrokeWidth: - lerpDouble(a.markerIconStrokeWidth, b.markerIconStrokeWidth, t), + lerpDouble(a.markerIconStrokeWidth, b.markerIconStrokeWidth, t)!, dataLabelTextStyle: TextStyle.lerp(a.dataLabelTextStyle, b.dataLabelTextStyle, t), bubbleColor: Color.lerp(a.bubbleColor, b.bubbleColor, t), bubbleStrokeColor: Color.lerp(a.bubbleStrokeColor, b.bubbleStrokeColor, t), bubbleStrokeWidth: - lerpDouble(a.bubbleStrokeWidth, b.bubbleStrokeWidth, t), + lerpDouble(a.bubbleStrokeWidth, b.bubbleStrokeWidth, t)!, bubbleHoverColor: Color.lerp(a.bubbleHoverColor, b.bubbleHoverColor, t), bubbleHoverStrokeColor: Color.lerp(a.bubbleHoverStrokeColor, b.bubbleHoverStrokeColor, t), @@ -966,14 +937,14 @@ class SfMapsThemeData with Diagnosticable { selectionStrokeColor: Color.lerp(a.selectionStrokeColor, b.selectionStrokeColor, t), selectionStrokeWidth: - lerpDouble(a.selectionStrokeWidth, b.selectionStrokeWidth, t), + lerpDouble(a.selectionStrokeWidth, b.selectionStrokeWidth, t)!, tooltipColor: Color.lerp(a.tooltipColor, b.tooltipColor, t), tooltipStrokeColor: Color.lerp(a.tooltipStrokeColor, b.tooltipStrokeColor, t), tooltipStrokeWidth: - lerpDouble(a.tooltipStrokeWidth, b.tooltipStrokeWidth, t), + lerpDouble(a.tooltipStrokeWidth, b.tooltipStrokeWidth, t)!, tooltipBorderRadius: BorderRadiusGeometry.lerp( - a.tooltipBorderRadius, b.tooltipBorderRadius, t), + a.tooltipBorderRadius, b.tooltipBorderRadius, t)!, toggledItemColor: Color.lerp(a.toggledItemColor, b.toggledItemColor, t), toggledItemStrokeColor: Color.lerp(a.toggledItemStrokeColor, b.toggledItemStrokeColor, t), @@ -1057,9 +1028,7 @@ class SfMapsThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfMapsThemeData defaultData = SfMapsThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfMapsThemeData defaultData = SfMapsThemeData(); properties.add(ColorProperty('layerColor', layerColor, defaultValue: defaultData.layerColor)); properties.add(ColorProperty('layerStrokeColor', layerStrokeColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart index 696b82307..136fb1e65 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/pdfviewer_theme.dart @@ -109,10 +109,26 @@ class SfPdfViewerTheme extends InheritedTheme { /// ``` @immutable class SfPdfViewerThemeData with Diagnosticable { + /// Create a [SfPdfViewerThemeData] given a set of exact values. + /// All the values must be specified. + /// + /// This will rarely be used directly. It is used by [lerp] to + /// create intermediate themes based on two themes created with the + /// [SfPdfViewerThemeData] constructor. + const SfPdfViewerThemeData({ + this.backgroundColor, + this.progressBarColor, + this.scrollStatusStyle, + this.scrollHeadStyle, + this.bookmarkViewStyle, + this.paginationDialogStyle, + this.hyperlinkDialogStyle, + this.passwordDialogStyle, + }); + /// Creating an argument constructor of SfPdfViewerThemeData class. - factory SfPdfViewerThemeData( - {Brightness? brightness, - Color? backgroundColor, + factory SfPdfViewerThemeData.raw( + {Color? backgroundColor, Color? progressBarColor, PdfScrollStatusStyle? scrollStatusStyle, PdfScrollHeadStyle? scrollHeadStyle, @@ -120,54 +136,7 @@ class SfPdfViewerThemeData with Diagnosticable { PdfPaginationDialogStyle? paginationDialogStyle, PdfHyperlinkDialogStyle? hyperlinkDialogStyle, PdfPasswordDialogStyle? passwordDialogStyle}) { - brightness = brightness ?? Brightness.light; - final bool isLight = brightness == Brightness.light; - backgroundColor ??= - isLight ? const Color(0xFFD6D6D6) : const Color(0xFF303030); - scrollHeadStyle ??= PdfScrollHeadStyle( - backgroundColor: - isLight ? const Color(0xFFFAFAFA) : const Color(0xFF424242), - ); - bookmarkViewStyle ??= PdfBookmarkViewStyle( - backgroundColor: isLight ? Colors.white : const Color(0xFF212121), - closeIconColor: isLight - ? Colors.black.withOpacity(0.54) - : Colors.white.withOpacity(0.54), - backIconColor: isLight - ? Colors.black.withOpacity(0.54) - : Colors.white.withOpacity(0.54), - headerBarColor: - isLight ? const Color(0xFFFAFAFA) : const Color(0xFF424242), - navigationIconColor: isLight - ? Colors.black.withOpacity(0.54) - : Colors.white.withOpacity(0.54), - selectionColor: isLight - ? const Color.fromRGBO(0, 0, 0, 0.08) - : const Color.fromRGBO(255, 255, 255, 0.12), - titleSeparatorColor: isLight - ? const Color.fromRGBO(0, 0, 0, 0.16) - : const Color.fromRGBO(255, 255, 255, 0.16), - ); - paginationDialogStyle ??= PdfPaginationDialogStyle( - backgroundColor: isLight ? Colors.white : const Color(0xFF424242), - ); - hyperlinkDialogStyle ??= PdfHyperlinkDialogStyle( - backgroundColor: isLight ? Colors.white : const Color(0xFF424242), - closeIconColor: isLight - ? Colors.black.withOpacity(0.6) - : Colors.white.withOpacity(0.6), - ); - passwordDialogStyle ??= PdfPasswordDialogStyle( - backgroundColor: isLight ? Colors.white : const Color(0xFF424242), - closeIconColor: isLight - ? Colors.black.withOpacity(0.6) - : Colors.white.withOpacity(0.6), - visibleIconColor: isLight - ? Colors.black.withOpacity(0.6) - : Colors.white.withOpacity(0.6), - ); - return SfPdfViewerThemeData.raw( - brightness: brightness, + return SfPdfViewerThemeData( backgroundColor: backgroundColor, progressBarColor: progressBarColor, scrollStatusStyle: scrollStatusStyle, @@ -178,54 +147,6 @@ class SfPdfViewerThemeData with Diagnosticable { passwordDialogStyle: passwordDialogStyle); } - /// Create a [SfPdfViewerThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfPdfViewerThemeData] constructor. - /// - const SfPdfViewerThemeData.raw({ - required this.brightness, - required this.backgroundColor, - required this.progressBarColor, - required this.scrollStatusStyle, - required this.scrollHeadStyle, - required this.bookmarkViewStyle, - required this.paginationDialogStyle, - required this.hyperlinkDialogStyle, - required this.passwordDialogStyle, - }); - - /// The brightness of the overall theme of the - /// application for [SfPdfViewer] widget. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).colorScheme.brightness], brightness for - /// [SfPdfViewer] widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// pdfViewerThemeData: SfPdfViewerThemeData( - /// brightness: Brightness.dark - /// ) - /// ), - /// child: SfPdfViewer.asset( - /// 'assets/flutter-succinctly.pdf', - /// ), - /// ), - /// ) - /// ); - /// } - ///``` - final Brightness? brightness; - /// Specifies the background color of [SfPdfViewer] widget. /// /// ```dart @@ -449,8 +370,7 @@ class SfPdfViewerThemeData with Diagnosticable { /// Creates a copy of this [SfPdfViewer] theme data object with the /// matching fields replaced with the non-null parameter values. SfPdfViewerThemeData copyWith( - {Brightness? brightness, - Color? backgroundColor, + {Color? backgroundColor, Color? progressBarColor, PdfScrollStatusStyle? scrollStatusStyle, PdfScrollHeadStyle? scrollHeadStyle, @@ -459,7 +379,6 @@ class SfPdfViewerThemeData with Diagnosticable { PdfHyperlinkDialogStyle? hyperlinkDialogStyle, PdfPasswordDialogStyle? passwordDialogStyle}) { return SfPdfViewerThemeData.raw( - brightness: brightness ?? this.brightness, backgroundColor: backgroundColor ?? this.backgroundColor, progressBarColor: progressBarColor ?? this.progressBarColor, scrollStatusStyle: scrollStatusStyle ?? this.scrollStatusStyle, @@ -505,7 +424,6 @@ class SfPdfViewerThemeData with Diagnosticable { } return other is SfPdfViewerThemeData && - other.brightness == brightness && other.backgroundColor == backgroundColor && other.progressBarColor == progressBarColor && other.scrollStatusStyle == scrollStatusStyle && @@ -534,9 +452,7 @@ class SfPdfViewerThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfPdfViewerThemeData defaultData = SfPdfViewerThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfPdfViewerThemeData defaultData = SfPdfViewerThemeData(); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: defaultData.backgroundColor)); properties.add(ColorProperty('progressBarColor', progressBarColor, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart index b830b2a51..5c12d304e 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/range_selector_theme.dart @@ -149,14 +149,10 @@ class SfRangeSelectorTheme extends InheritedTheme { /// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), /// for customizing the visual appearance of the range selector. class SfRangeSelectorThemeData extends SfRangeSliderThemeData { - /// Returns a new instance of [SfRangeSelectorThemeData.raw] - /// for the given values. - /// - /// If any of the values are null, the default values will be set. - factory SfRangeSelectorThemeData( - {Brightness? brightness, - double? activeTrackHeight, - double? inactiveTrackHeight, + /// Creating an argument constructor of SfRangeSelectorThemeData class. + const SfRangeSelectorThemeData( + {double activeTrackHeight = 6.0, + double inactiveTrackHeight = 4.0, Size? tickSize, Size? minorTickSize, Offset? tickOffset, @@ -183,8 +179,8 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { Color? disabledActiveDividerColor, Color? disabledInactiveDividerColor, Color? disabledThumbColor, - Color? activeRegionColor, - Color? inactiveRegionColor, + this.activeRegionColor, + this.inactiveRegionColor, Color? tooltipBackgroundColor, Color? overlappingTooltipStrokeColor, Color? thumbStrokeColor, @@ -192,13 +188,105 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { Color? activeDividerStrokeColor, Color? inactiveDividerStrokeColor, double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, + double overlayRadius = 24.0, + double thumbRadius = 10.0, double? activeDividerRadius, double? inactiveDividerRadius, double? thumbStrokeWidth, double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + double? inactiveDividerStrokeWidth}) + : super( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + overlappingThumbStrokeColor: overlappingThumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius); + + /// Returns a new instance of [SfRangeSelectorThemeData] for the given values. + factory SfRangeSelectorThemeData.raw({ + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? activeRegionColor, + Color? inactiveRegionColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; @@ -207,8 +295,7 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfRangeSelectorThemeData.raw( - brightness: brightness, + return SfRangeSelectorThemeData( activeTrackHeight: activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight, tickSize: tickSize, @@ -255,103 +342,6 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { trackCornerRadius: trackCornerRadius); } - /// Create a [SfRangeSelectorThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfRangeSelectorThemeData] constructor. - const SfRangeSelectorThemeData.raw({ - required Brightness brightness, - required double activeTrackHeight, - required double inactiveTrackHeight, - required Size? tickSize, - required Size? minorTickSize, - required Offset? tickOffset, - required Offset? labelOffset, - required TextStyle? inactiveLabelStyle, - required TextStyle? activeLabelStyle, - required TextStyle? tooltipTextStyle, - required Color? inactiveTrackColor, - required Color? activeTrackColor, - required Color? thumbColor, - required Color? thumbStrokeColor, - required Color? overlappingThumbStrokeColor, - required Color? activeDividerStrokeColor, - required Color? inactiveDividerStrokeColor, - required Color? activeTickColor, - required Color? inactiveTickColor, - required Color? disabledActiveTickColor, - required Color? disabledInactiveTickColor, - required Color? activeMinorTickColor, - required Color? inactiveMinorTickColor, - required Color? disabledActiveMinorTickColor, - required Color? disabledInactiveMinorTickColor, - required Color? overlayColor, - required Color? inactiveDividerColor, - required Color? activeDividerColor, - required Color? disabledActiveTrackColor, - required Color? disabledInactiveTrackColor, - required Color? disabledActiveDividerColor, - required Color? disabledInactiveDividerColor, - required Color? disabledThumbColor, - required this.activeRegionColor, - required this.inactiveRegionColor, - required Color? tooltipBackgroundColor, - required Color? overlappingTooltipStrokeColor, - required double? trackCornerRadius, - required double overlayRadius, - required double thumbRadius, - required double? activeDividerRadius, - required double? inactiveDividerRadius, - required double? thumbStrokeWidth, - required double? activeDividerStrokeWidth, - required double? inactiveDividerStrokeWidth, - }) : super.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - overlappingThumbStrokeColor: overlappingThumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlappingTooltipStrokeColor: overlappingTooltipStrokeColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); - /// Specifies the color for the active region of the /// child in the [SfRangeSelector]. /// @@ -479,7 +469,7 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { double? inactiveDividerStrokeWidth, }) { return SfRangeSelectorThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -554,9 +544,9 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { } return SfRangeSelectorThemeData( activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, tickSize: Size.lerp(a.tickSize, b.tickSize, t), minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), @@ -620,8 +610,8 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { overlappingTooltipStrokeColor: Color.lerp(a.overlappingTooltipStrokeColor, b.overlappingTooltipStrokeColor, t), // ignore: lines_longer_than_80_chars trackCornerRadius: lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, // ignore: lines_longer_than_80_chars activeDividerRadius: lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), // ignore: lines_longer_than_80_chars @@ -643,7 +633,6 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { } return other is SfRangeSelectorThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -694,7 +683,6 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { @override int get hashCode { return Object.hashAll([ - brightness, activeTrackHeight, inactiveTrackHeight, tickSize, @@ -745,9 +733,7 @@ class SfRangeSelectorThemeData extends SfRangeSliderThemeData { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfRangeSelectorThemeData defaultData = SfRangeSelectorThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfRangeSelectorThemeData defaultData = SfRangeSelectorThemeData(); properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, defaultValue: defaultData.activeTrackHeight)); properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart index 96fd1fa0c..360a3da79 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/range_slider_theme.dart @@ -1,8 +1,6 @@ import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - import '../../theme.dart'; /// Applies a theme to descendant Syncfusion range slider widgets. @@ -35,7 +33,6 @@ class SfRangeSliderTheme extends InheritedTheme { @override bool updateShouldNotify(SfRangeSliderTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final SfRangeSliderTheme? ancestorTheme = @@ -85,14 +82,10 @@ class SfRangeSliderTheme extends InheritedTheme { /// and [SfThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfThemeData-class.html), /// for customizing the visual appearance of the range slider. class SfRangeSliderThemeData extends SfSliderThemeData { - /// Returns a new instance of [SfRangeSliderThemeData.raw] - /// for the given values. - /// - /// If any of the values are null, the default values will be set. - factory SfRangeSliderThemeData( - {Brightness? brightness, - double? activeTrackHeight, - double? inactiveTrackHeight, + /// Creating an argument constructor of SfRangeSliderThemeData class. + const SfRangeSliderThemeData( + {double activeTrackHeight = 6.0, + double inactiveTrackHeight = 4.0, Size? tickSize, Size? minorTickSize, Offset? tickOffset, @@ -120,27 +113,114 @@ class SfRangeSliderThemeData extends SfSliderThemeData { Color? disabledInactiveDividerColor, Color? disabledThumbColor, Color? tooltipBackgroundColor, - Color? overlappingTooltipStrokeColor, + this.overlappingTooltipStrokeColor, Color? thumbStrokeColor, - Color? overlappingThumbStrokeColor, + this.overlappingThumbStrokeColor, Color? activeDividerStrokeColor, Color? inactiveDividerStrokeColor, double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, + double overlayRadius = 24.0, + double thumbRadius = 10.0, double? activeDividerRadius, double? inactiveDividerRadius, double? thumbStrokeWidth, double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + double? inactiveDividerStrokeWidth}) + : super( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius); + + /// Returns a new instance of [SfRangeSliderThemeData] for the given values. + factory SfRangeSliderThemeData.raw({ + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? thumbStrokeColor, + Color? overlappingThumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + Color? overlappingTooltipStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfRangeSliderThemeData.raw( - brightness: brightness, + return SfRangeSliderThemeData( activeTrackHeight: activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight, tickSize: tickSize, @@ -191,92 +271,6 @@ class SfRangeSliderThemeData extends SfSliderThemeData { /// This will rarely be used directly. It is used by [lerp] to /// create intermediate themes based on two themes created with the /// [SfRangeSliderThemeData] constructor. - const SfRangeSliderThemeData.raw({ - required Brightness brightness, - required double activeTrackHeight, - required double inactiveTrackHeight, - required Size? tickSize, - required Size? minorTickSize, - required Offset? tickOffset, - required Offset? labelOffset, - required TextStyle? inactiveLabelStyle, - required TextStyle? activeLabelStyle, - required TextStyle? tooltipTextStyle, - required Color? inactiveTrackColor, - required Color? activeTrackColor, - required Color? thumbColor, - required Color? thumbStrokeColor, - required this.overlappingThumbStrokeColor, - required Color? activeDividerStrokeColor, - required Color? inactiveDividerStrokeColor, - required Color? activeTickColor, - required Color? inactiveTickColor, - required Color? disabledActiveTickColor, - required Color? disabledInactiveTickColor, - required Color? activeMinorTickColor, - required Color? inactiveMinorTickColor, - required Color? disabledActiveMinorTickColor, - required Color? disabledInactiveMinorTickColor, - required Color? overlayColor, - required Color? inactiveDividerColor, - required Color? activeDividerColor, - required Color? disabledActiveTrackColor, - required Color? disabledInactiveTrackColor, - required Color? disabledActiveDividerColor, - required Color? disabledInactiveDividerColor, - required Color? disabledThumbColor, - required Color? tooltipBackgroundColor, - required this.overlappingTooltipStrokeColor, - required double? trackCornerRadius, - required double overlayRadius, - required double thumbRadius, - required double? activeDividerRadius, - required double? inactiveDividerRadius, - required double? thumbStrokeWidth, - required double? activeDividerStrokeWidth, - required double? inactiveDividerStrokeWidth, - }) : super.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); /// Specifies the stroke color for the thumbs when they overlap in the /// [SfRangeSlider], and [SfRangeSelector]. @@ -391,7 +385,7 @@ class SfRangeSliderThemeData extends SfSliderThemeData { double? inactiveDividerStrokeWidth, }) { return SfRangeSliderThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -464,9 +458,9 @@ class SfRangeSliderThemeData extends SfSliderThemeData { } return SfRangeSliderThemeData( activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, tickSize: Size.lerp(a.tickSize, b.tickSize, t), minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), @@ -530,8 +524,8 @@ class SfRangeSliderThemeData extends SfSliderThemeData { // ignore: lines_longer_than_80_chars trackCornerRadius: lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, // ignore: lines_longer_than_80_chars activeDividerRadius: lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), @@ -554,7 +548,6 @@ class SfRangeSliderThemeData extends SfSliderThemeData { } return other is SfRangeSliderThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -603,7 +596,6 @@ class SfRangeSliderThemeData extends SfSliderThemeData { @override int get hashCode { return Object.hashAll([ - brightness, activeTrackHeight, inactiveTrackHeight, tickSize, @@ -652,9 +644,7 @@ class SfRangeSliderThemeData extends SfSliderThemeData { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfRangeSliderThemeData defaultData = SfRangeSliderThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfRangeSliderThemeData defaultData = SfRangeSliderThemeData(); properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, defaultValue: defaultData.activeTrackHeight)); properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, diff --git a/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart index 930f234f5..c050b183a 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/slider_theme.dart @@ -81,151 +81,144 @@ class SfSliderTheme extends InheritedTheme { /// for customizing the visual appearance of the slider. @immutable class SfSliderThemeData with Diagnosticable { - /// Returns a new instance of [SfSliderThemeData.raw] for the given values. - /// - /// If any of the values are null, the default values will be set. - factory SfSliderThemeData( - {Brightness? brightness, - double? activeTrackHeight, - double? inactiveTrackHeight, - Size? tickSize, - Size? minorTickSize, - Offset? tickOffset, - Offset? labelOffset, - TextStyle? inactiveLabelStyle, - TextStyle? activeLabelStyle, - TextStyle? tooltipTextStyle, - Color? inactiveTrackColor, - Color? activeTrackColor, - Color? thumbColor, - Color? activeTickColor, - Color? inactiveTickColor, - Color? disabledActiveTickColor, - Color? disabledInactiveTickColor, - Color? activeMinorTickColor, - Color? inactiveMinorTickColor, - Color? disabledActiveMinorTickColor, - Color? disabledInactiveMinorTickColor, - Color? overlayColor, - Color? inactiveDividerColor, - Color? activeDividerColor, - Color? disabledActiveTrackColor, - Color? disabledInactiveTrackColor, - Color? disabledActiveDividerColor, - Color? disabledInactiveDividerColor, - Color? disabledThumbColor, - Color? tooltipBackgroundColor, - Color? thumbStrokeColor, - Color? activeDividerStrokeColor, - Color? inactiveDividerStrokeColor, - double? trackCornerRadius, - double? overlayRadius, - double? thumbRadius, - double? activeDividerRadius, - double? inactiveDividerRadius, - double? thumbStrokeWidth, - double? activeDividerStrokeWidth, - double? inactiveDividerStrokeWidth}) { + /// Creating an argument constructor of SfSliderThemeData class. + const SfSliderThemeData({ + this.activeTrackHeight = 6.0, + this.inactiveTrackHeight = 4.0, + this.tickSize, + this.minorTickSize, + this.tickOffset, + this.labelOffset, + this.inactiveLabelStyle, + this.activeLabelStyle, + this.tooltipTextStyle, + this.inactiveTrackColor, + this.activeTrackColor, + this.thumbColor, + this.thumbStrokeColor, + this.activeDividerStrokeColor, + this.inactiveDividerStrokeColor, + this.activeTickColor, + this.inactiveTickColor, + this.disabledActiveTickColor, + this.disabledInactiveTickColor, + this.activeMinorTickColor, + this.inactiveMinorTickColor, + this.disabledActiveMinorTickColor, + this.disabledInactiveMinorTickColor, + this.overlayColor, + this.inactiveDividerColor, + this.activeDividerColor, + this.disabledActiveTrackColor, + this.disabledInactiveTrackColor, + this.disabledActiveDividerColor, + this.disabledInactiveDividerColor, + this.disabledThumbColor, + this.tooltipBackgroundColor, + this.trackCornerRadius, + this.overlayRadius = 24.0, + this.thumbRadius = 10.0, + this.activeDividerRadius, + this.inactiveDividerRadius, + this.thumbStrokeWidth, + this.activeDividerStrokeWidth, + this.inactiveDividerStrokeWidth, + }); + + /// Returns a new instance of [SfSliderThemeData] for the given values. + factory SfSliderThemeData.raw({ + Brightness? brightness, + double? activeTrackHeight, + double? inactiveTrackHeight, + Size? tickSize, + Size? minorTickSize, + Offset? tickOffset, + Offset? labelOffset, + TextStyle? inactiveLabelStyle, + TextStyle? activeLabelStyle, + TextStyle? tooltipTextStyle, + Color? inactiveTrackColor, + Color? activeTrackColor, + Color? thumbColor, + Color? activeTickColor, + Color? inactiveTickColor, + Color? disabledActiveTickColor, + Color? disabledInactiveTickColor, + Color? activeMinorTickColor, + Color? inactiveMinorTickColor, + Color? disabledActiveMinorTickColor, + Color? disabledInactiveMinorTickColor, + Color? overlayColor, + Color? inactiveDividerColor, + Color? activeDividerColor, + Color? disabledActiveTrackColor, + Color? disabledInactiveTrackColor, + Color? disabledActiveDividerColor, + Color? disabledInactiveDividerColor, + Color? disabledThumbColor, + Color? tooltipBackgroundColor, + Color? thumbStrokeColor, + Color? activeDividerStrokeColor, + Color? inactiveDividerStrokeColor, + double? trackCornerRadius, + double? overlayRadius, + double? thumbRadius, + double? activeDividerRadius, + double? inactiveDividerRadius, + double? thumbStrokeWidth, + double? activeDividerStrokeWidth, + double? inactiveDividerStrokeWidth, + }) { brightness = brightness ?? Brightness.light; activeTrackHeight ??= 6.0; inactiveTrackHeight ??= 4.0; overlayRadius ??= 24.0; thumbRadius ??= 10.0; - return SfSliderThemeData.raw( - brightness: brightness, - activeTrackHeight: activeTrackHeight, - inactiveTrackHeight: inactiveTrackHeight, - tickSize: tickSize, - minorTickSize: minorTickSize, - tickOffset: tickOffset, - labelOffset: labelOffset, - inactiveLabelStyle: inactiveLabelStyle, - activeLabelStyle: activeLabelStyle, - tooltipTextStyle: tooltipTextStyle, - inactiveTrackColor: inactiveTrackColor, - activeTrackColor: activeTrackColor, - inactiveDividerColor: inactiveDividerColor, - activeDividerColor: activeDividerColor, - thumbColor: thumbColor, - thumbStrokeColor: thumbStrokeColor, - activeDividerStrokeColor: activeDividerStrokeColor, - inactiveDividerStrokeColor: inactiveDividerStrokeColor, - overlayColor: overlayColor, - activeTickColor: activeTickColor, - inactiveTickColor: inactiveTickColor, - disabledActiveTickColor: disabledActiveTickColor, - disabledInactiveTickColor: disabledInactiveTickColor, - activeMinorTickColor: activeMinorTickColor, - inactiveMinorTickColor: inactiveMinorTickColor, - disabledActiveMinorTickColor: disabledActiveMinorTickColor, - disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, - disabledActiveTrackColor: disabledActiveTrackColor, - disabledInactiveTrackColor: disabledInactiveTrackColor, - disabledActiveDividerColor: disabledActiveDividerColor, - disabledInactiveDividerColor: disabledInactiveDividerColor, - disabledThumbColor: disabledThumbColor, - tooltipBackgroundColor: tooltipBackgroundColor, - overlayRadius: overlayRadius, - thumbRadius: thumbRadius, - activeDividerRadius: activeDividerRadius, - inactiveDividerRadius: inactiveDividerRadius, - thumbStrokeWidth: thumbStrokeWidth, - activeDividerStrokeWidth: activeDividerStrokeWidth, - inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, - trackCornerRadius: trackCornerRadius); + return SfSliderThemeData( + activeTrackHeight: activeTrackHeight, + inactiveTrackHeight: inactiveTrackHeight, + tickSize: tickSize, + minorTickSize: minorTickSize, + tickOffset: tickOffset, + labelOffset: labelOffset, + inactiveLabelStyle: inactiveLabelStyle, + activeLabelStyle: activeLabelStyle, + tooltipTextStyle: tooltipTextStyle, + inactiveTrackColor: inactiveTrackColor, + activeTrackColor: activeTrackColor, + inactiveDividerColor: inactiveDividerColor, + activeDividerColor: activeDividerColor, + thumbColor: thumbColor, + thumbStrokeColor: thumbStrokeColor, + activeDividerStrokeColor: activeDividerStrokeColor, + inactiveDividerStrokeColor: inactiveDividerStrokeColor, + overlayColor: overlayColor, + activeTickColor: activeTickColor, + inactiveTickColor: inactiveTickColor, + disabledActiveTickColor: disabledActiveTickColor, + disabledInactiveTickColor: disabledInactiveTickColor, + activeMinorTickColor: activeMinorTickColor, + inactiveMinorTickColor: inactiveMinorTickColor, + disabledActiveMinorTickColor: disabledActiveMinorTickColor, + disabledInactiveMinorTickColor: disabledInactiveMinorTickColor, + disabledActiveTrackColor: disabledActiveTrackColor, + disabledInactiveTrackColor: disabledInactiveTrackColor, + disabledActiveDividerColor: disabledActiveDividerColor, + disabledInactiveDividerColor: disabledInactiveDividerColor, + disabledThumbColor: disabledThumbColor, + tooltipBackgroundColor: tooltipBackgroundColor, + overlayRadius: overlayRadius, + thumbRadius: thumbRadius, + activeDividerRadius: activeDividerRadius, + inactiveDividerRadius: inactiveDividerRadius, + thumbStrokeWidth: thumbStrokeWidth, + activeDividerStrokeWidth: activeDividerStrokeWidth, + inactiveDividerStrokeWidth: inactiveDividerStrokeWidth, + trackCornerRadius: trackCornerRadius, + ); } - /// Create a [SfSliderThemeData] given a set of exact values. - /// All the values must be specified. - /// - /// This will rarely be used directly. It is used by [lerp] to - /// create intermediate themes based on two themes created with the - /// [SfSliderThemeData] constructor. - const SfSliderThemeData.raw({ - required this.brightness, - required this.activeTrackHeight, - required this.inactiveTrackHeight, - required this.tickSize, - required this.minorTickSize, - required this.tickOffset, - required this.labelOffset, - required this.inactiveLabelStyle, - required this.activeLabelStyle, - required this.tooltipTextStyle, - required this.inactiveTrackColor, - required this.activeTrackColor, - required this.thumbColor, - required this.thumbStrokeColor, - required this.activeDividerStrokeColor, - required this.inactiveDividerStrokeColor, - required this.activeTickColor, - required this.inactiveTickColor, - required this.disabledActiveTickColor, - required this.disabledInactiveTickColor, - required this.activeMinorTickColor, - required this.inactiveMinorTickColor, - required this.disabledActiveMinorTickColor, - required this.disabledInactiveMinorTickColor, - required this.overlayColor, - required this.inactiveDividerColor, - required this.activeDividerColor, - required this.disabledActiveTrackColor, - required this.disabledInactiveTrackColor, - required this.disabledActiveDividerColor, - required this.disabledInactiveDividerColor, - required this.disabledThumbColor, - required this.tooltipBackgroundColor, - required this.trackCornerRadius, - required this.overlayRadius, - required this.thumbRadius, - required this.activeDividerRadius, - required this.inactiveDividerRadius, - required this.thumbStrokeWidth, - required this.activeDividerStrokeWidth, - required this.inactiveDividerStrokeWidth, - }); - /// Creates a copy of this theme but with the given fields /// replaced with the new values. SfSliderThemeData copyWith({ @@ -274,7 +267,7 @@ class SfSliderThemeData with Diagnosticable { double? inactiveDividerStrokeWidth, }) { return SfSliderThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, activeTrackHeight: activeTrackHeight ?? this.activeTrackHeight, inactiveTrackHeight: inactiveTrackHeight ?? this.inactiveTrackHeight, tickSize: tickSize ?? this.tickSize, @@ -343,9 +336,9 @@ class SfSliderThemeData with Diagnosticable { } return SfSliderThemeData( activeTrackHeight: - lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t), + lerpDouble(a!.activeTrackHeight, b!.activeTrackHeight, t)!, inactiveTrackHeight: - lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t), + lerpDouble(a.inactiveTrackHeight, b.inactiveTrackHeight, t)!, tickSize: Size.lerp(a.tickSize, b.tickSize, t), minorTickSize: Size.lerp(a.minorTickSize, b.minorTickSize, t), tickOffset: Offset.lerp(a.tickOffset, b.tickOffset, t), @@ -401,8 +394,8 @@ class SfSliderThemeData with Diagnosticable { Color.lerp(a.tooltipBackgroundColor, b.tooltipBackgroundColor, t), trackCornerRadius: lerpDouble(a.trackCornerRadius, b.trackCornerRadius, t), - overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t), - thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t), + overlayRadius: lerpDouble(a.overlayRadius, b.overlayRadius, t)!, + thumbRadius: lerpDouble(a.thumbRadius, b.thumbRadius, t)!, activeDividerRadius: lerpDouble(a.activeDividerRadius, b.activeDividerRadius, t), inactiveDividerRadius: @@ -425,7 +418,6 @@ class SfSliderThemeData with Diagnosticable { } return other is SfSliderThemeData && - other.brightness == brightness && other.activeTrackHeight == activeTrackHeight && other.inactiveTrackHeight == inactiveTrackHeight && other.tickSize == tickSize && @@ -472,7 +464,6 @@ class SfSliderThemeData with Diagnosticable { @override int get hashCode { return Object.hashAll([ - brightness, activeTrackHeight, inactiveTrackHeight, tickSize, @@ -519,9 +510,7 @@ class SfSliderThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfSliderThemeData defaultData = SfSliderThemeData(); - properties.add(EnumProperty('brightness', brightness, - defaultValue: defaultData.brightness)); + const SfSliderThemeData defaultData = SfSliderThemeData(); properties.add(DoubleProperty('activeTrackHeight', activeTrackHeight, defaultValue: defaultData.activeTrackHeight)); properties.add(DoubleProperty('inactiveTrackHeight', inactiveTrackHeight, @@ -622,48 +611,6 @@ class SfSliderThemeData with Diagnosticable { defaultValue: defaultData.inactiveDividerStrokeWidth)); } - /// Specifies the light and dark theme for the [SfSlider], - /// [SfRangeSlider], and [SfRangeSelector]. - /// - /// This theme applies to all the [SfSlider] which are - /// added as children of [SfSliderTheme]. - /// - /// This theme applies to all the [SfRangeSlider] which are - /// added as children of [SfRangeSliderTheme]. - /// - /// This theme applies to all the [SfRangeSelector] which are - /// added as children of [SfRangeSelectorTheme]. - /// - /// This snippet shows how to set brightness in [SfRangeSliderThemeData]. - /// - /// ```dart - /// SfRangeValues _values = SfRangeValues(4.0, 7.0); - /// - /// Scaffold( - /// body: Center( - /// child: SfRangeSliderTheme( - /// data: SfRangeSliderThemeData( - /// brightness: Brightness.dark, - /// ), - /// child: SfRangeSlider( - /// min: 2.0, - /// max: 10.0, - /// values: _values, - /// interval: 2, - /// showTicks: true, - /// showLabels: true, - /// onChanged: (SfRangeValues newValues){ - /// setState(() { - /// _values = newValues; - /// }); - /// }, - /// ) - /// ), - /// ) - /// ) - /// ``` - final Brightness brightness; - /// Specifies the height for the active track in the [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. /// diff --git a/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart b/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart index 13ea7d6fa..6b241261c 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/theme_widget.dart @@ -1,6 +1,5 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; - import 'barcodes_theme.dart'; import 'calendar_theme.dart'; import 'charts_theme.dart'; @@ -13,6 +12,7 @@ import 'pdfviewer_theme.dart'; import 'range_selector_theme.dart'; import 'range_slider_theme.dart'; import 'slider_theme.dart'; +import 'spark_charts_theme.dart'; import 'treemap_theme.dart'; /// Applies a theme to descendant Syncfusion widgets. @@ -90,7 +90,6 @@ class SfTheme extends StatelessWidget { /// } /// ``` final SfThemeData? data; - //ignore: unused_field static final SfThemeData _kFallbackTheme = SfThemeData.fallback(); @@ -118,13 +117,10 @@ class SfTheme extends StatelessWidget { class _SfInheritedTheme extends InheritedTheme { const _SfInheritedTheme({Key? key, this.data, required Widget child}) : super(key: key, child: child); - final SfThemeData? data; - @override bool updateShouldNotify(_SfInheritedTheme oldWidget) => data != oldWidget.data; - @override Widget wrap(BuildContext context, Widget child) { final _SfInheritedTheme? ancestorTheme = @@ -165,6 +161,7 @@ class SfThemeData with Diagnosticable { {Brightness? brightness, SfPdfViewerThemeData? pdfViewerThemeData, SfChartThemeData? chartThemeData, + SfSparkChartThemeData? sparkChartThemeData, SfCalendarThemeData? calendarThemeData, SfDataGridThemeData? dataGridThemeData, SfDataPagerThemeData? dataPagerThemeData, @@ -177,33 +174,38 @@ class SfThemeData with Diagnosticable { SfMapsThemeData? mapsThemeData, SfTreemapThemeData? treemapThemeData}) { brightness ??= Brightness.light; - pdfViewerThemeData = - pdfViewerThemeData ?? SfPdfViewerThemeData(brightness: brightness); - chartThemeData = chartThemeData ?? SfChartThemeData(brightness: brightness); + pdfViewerThemeData = pdfViewerThemeData ?? SfPdfViewerThemeData.raw(); + sparkChartThemeData = sparkChartThemeData ?? + SfSparkChartThemeData.raw(brightness: brightness); + chartThemeData = + chartThemeData ?? SfChartThemeData.raw(brightness: brightness); calendarThemeData = - calendarThemeData ?? SfCalendarThemeData(brightness: brightness); + calendarThemeData ?? SfCalendarThemeData.raw(brightness: brightness); dataGridThemeData = - dataGridThemeData ?? SfDataGridThemeData(brightness: brightness); + dataGridThemeData ?? SfDataGridThemeData.raw(brightness: brightness); dateRangePickerThemeData = dateRangePickerThemeData ?? - SfDateRangePickerThemeData(brightness: brightness); + SfDateRangePickerThemeData.raw(brightness: brightness); barcodeThemeData = - barcodeThemeData ?? SfBarcodeThemeData(brightness: brightness); - gaugeThemeData = gaugeThemeData ?? SfGaugeThemeData(brightness: brightness); + barcodeThemeData ?? SfBarcodeThemeData.raw(brightness: brightness); + gaugeThemeData = + gaugeThemeData ?? SfGaugeThemeData.raw(brightness: brightness); sliderThemeData = - sliderThemeData ?? SfSliderThemeData(brightness: brightness); + sliderThemeData ?? SfSliderThemeData.raw(brightness: brightness); rangeSelectorThemeData = rangeSelectorThemeData ?? - SfRangeSelectorThemeData(brightness: brightness); - rangeSliderThemeData = - rangeSliderThemeData ?? SfRangeSliderThemeData(brightness: brightness); - mapsThemeData = mapsThemeData ?? SfMapsThemeData(brightness: brightness); + SfRangeSelectorThemeData.raw(brightness: brightness); + rangeSliderThemeData = rangeSliderThemeData ?? + SfRangeSliderThemeData.raw(brightness: brightness); + mapsThemeData = + mapsThemeData ?? SfMapsThemeData.raw(brightness: brightness); treemapThemeData = - treemapThemeData ?? SfTreemapThemeData(brightness: brightness); + treemapThemeData ?? SfTreemapThemeData.raw(brightness: brightness); dataPagerThemeData = - dataPagerThemeData ?? SfDataPagerThemeData(brightness: brightness); + dataPagerThemeData ?? SfDataPagerThemeData.raw(brightness: brightness); return SfThemeData.raw( brightness: brightness, pdfViewerThemeData: pdfViewerThemeData, chartThemeData: chartThemeData, + sparkChartThemeData: sparkChartThemeData, calendarThemeData: calendarThemeData, dataGridThemeData: dataGridThemeData, dataPagerThemeData: dataPagerThemeData, @@ -228,6 +230,7 @@ class SfThemeData with Diagnosticable { {required this.brightness, required this.pdfViewerThemeData, required this.chartThemeData, + required this.sparkChartThemeData, required this.calendarThemeData, required this.dataGridThemeData, required this.dateRangePickerThemeData, @@ -317,6 +320,25 @@ class SfThemeData with Diagnosticable { /// ``` final SfChartThemeData chartThemeData; + /// Defines the default configuration of spark chart widgets. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// sparkchartThemeData: SfSparkChartThemeData() + /// ), + /// child: SfCartesianChart(), + /// ), + /// ) + /// ); + /// } + /// ``` + final SfSparkChartThemeData sparkChartThemeData; + /// Defines the default configuration of datagrid widgets. /// /// ```dart @@ -532,6 +554,7 @@ class SfThemeData with Diagnosticable { Brightness? brightness, SfPdfViewerThemeData? pdfViewerThemeData, SfChartThemeData? chartThemeData, + SfSparkChartThemeData? sparkChartThemeData, SfCalendarThemeData? calendarThemeData, SfDataGridThemeData? dataGridThemeData, SfDateRangePickerThemeData? dateRangePickerThemeData, @@ -548,6 +571,7 @@ class SfThemeData with Diagnosticable { brightness: brightness ?? this.brightness, pdfViewerThemeData: pdfViewerThemeData ?? this.pdfViewerThemeData, chartThemeData: chartThemeData ?? this.chartThemeData, + sparkChartThemeData: sparkChartThemeData ?? this.sparkChartThemeData, calendarThemeData: calendarThemeData ?? this.calendarThemeData, dataGridThemeData: dataGridThemeData ?? this.dataGridThemeData, dataPagerThemeData: dataPagerThemeData ?? this.dataPagerThemeData, @@ -567,13 +591,14 @@ class SfThemeData with Diagnosticable { static SfThemeData lerp(SfThemeData? a, SfThemeData? b, double t) { assert(a != null); assert(b != null); - return SfThemeData.raw( brightness: t < 0.5 ? a!.brightness : b!.brightness, pdfViewerThemeData: SfPdfViewerThemeData.lerp( a!.pdfViewerThemeData, b!.pdfViewerThemeData, t)!, chartThemeData: SfChartThemeData.lerp(a.chartThemeData, b.chartThemeData, t)!, + sparkChartThemeData: SfSparkChartThemeData.lerp( + a.sparkChartThemeData, b.sparkChartThemeData, t)!, calendarThemeData: SfCalendarThemeData.lerp( a.calendarThemeData, b.calendarThemeData, t)!, dataGridThemeData: SfDataGridThemeData.lerp( @@ -603,11 +628,11 @@ class SfThemeData with Diagnosticable { if (other.runtimeType != runtimeType) { return false; } - return other is SfThemeData && other.brightness == brightness && other.pdfViewerThemeData == pdfViewerThemeData && other.chartThemeData == chartThemeData && + other.sparkChartThemeData == sparkChartThemeData && other.calendarThemeData == calendarThemeData && other.dataGridThemeData == dataGridThemeData && other.dataPagerThemeData == dataPagerThemeData && @@ -627,6 +652,7 @@ class SfThemeData with Diagnosticable { brightness, pdfViewerThemeData, chartThemeData, + sparkChartThemeData, calendarThemeData, dataGridThemeData, dataPagerThemeData, @@ -654,6 +680,9 @@ class SfThemeData with Diagnosticable { properties.add(DiagnosticsProperty( 'chartThemeData', chartThemeData, defaultValue: defaultData.chartThemeData)); + properties.add(DiagnosticsProperty( + 'sparkChartThemeData', sparkChartThemeData, + defaultValue: defaultData.sparkChartThemeData)); properties.add(DiagnosticsProperty( 'calendarThemeData', calendarThemeData, defaultValue: defaultData.calendarThemeData)); diff --git a/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart b/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart index 8f0d3345f..ca5e842c6 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/treemap_theme.dart @@ -115,56 +115,25 @@ class SfTreemapTheme extends InheritedTheme { @immutable class SfTreemapThemeData with Diagnosticable { - ///Initialize the sfTreemap theme data - factory SfTreemapThemeData({ - Brightness? brightness, - TextStyle? legendTextStyle, - }) { - brightness = brightness ?? Brightness.light; - - return SfTreemapThemeData.raw( - brightness: brightness, legendTextStyle: legendTextStyle); - } - /// Create a [SfTreemapThemeData] given a set of exact values. /// All the values must be specified. /// /// This will rarely be used directly. It is used by [lerp] to /// create intermediate themes based on two themes created with the /// [SfTreemapThemeData] constructor. - const SfTreemapThemeData.raw({ - required this.brightness, - required this.legendTextStyle, + const SfTreemapThemeData({ + this.legendTextStyle, }); - /// The brightness of the overall theme of the - /// application for the treemap widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// treemap widgets will be applied. - /// - /// Also refer [Brightness]. - /// - /// - /// ```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(), - /// body: Center( - /// child: SfTheme( - /// data: SfThemeData( - /// treemapThemeData: SfTreemapThemeData( - /// brightness: Brightness.dark - /// ), - /// ), - /// child: SfTreemap(), - /// ), - /// ) - /// ); - ///} - /// ``` - final Brightness brightness; + ///Initialize the sfTreemap theme data + factory SfTreemapThemeData.raw({ + Brightness? brightness, + TextStyle? legendTextStyle, + }) { + brightness = brightness ?? Brightness.light; + + return SfTreemapThemeData(legendTextStyle: legendTextStyle); + } /// Specifies the legend text style of treemap widgets. /// @@ -194,7 +163,7 @@ class SfTreemapThemeData with Diagnosticable { TextStyle? legendTextStyle, }) { return SfTreemapThemeData.raw( - brightness: brightness ?? this.brightness, + brightness: brightness, legendTextStyle: legendTextStyle ?? this.legendTextStyle); } @@ -231,7 +200,7 @@ class SfTreemapThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - final SfTreemapThemeData defaultData = SfTreemapThemeData(); + const SfTreemapThemeData defaultData = SfTreemapThemeData(); properties.add(DiagnosticsProperty( 'legendTextStyle', legendTextStyle, defaultValue: defaultData.legendTextStyle)); diff --git a/packages/syncfusion_flutter_core/lib/theme.dart b/packages/syncfusion_flutter_core/lib/theme.dart index 7ea2594d3..76f172199 100644 --- a/packages/syncfusion_flutter_core/lib/theme.dart +++ b/packages/syncfusion_flutter_core/lib/theme.dart @@ -10,5 +10,6 @@ export './src/theme/pdfviewer_theme.dart'; export './src/theme/range_selector_theme.dart'; export './src/theme/range_slider_theme.dart'; export './src/theme/slider_theme.dart'; +export './src/theme/spark_charts_theme.dart'; export './src/theme/theme_widget.dart'; export './src/theme/treemap_theme.dart'; diff --git a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart index 7bb22af66..9faa99398 100644 --- a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart +++ b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart @@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; // ignore: depend_on_referenced_packages +import 'package:flutter_test/flutter_test.dart'; import 'core.dart'; part 'src/tooltip/tooltip.dart'; part 'src/test/tooltip_tests.dart'; diff --git a/packages/syncfusion_flutter_core/pubspec.yaml b/packages/syncfusion_flutter_core/pubspec.yaml index ab51efbc7..e322004be 100644 --- a/packages/syncfusion_flutter_core/pubspec.yaml +++ b/packages/syncfusion_flutter_core/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_core description: Syncfusion Flutter Core is a dependent package for all the Syncfusion Flutter widgets. -version: 23.2.5 +version: 24.2.7 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_core environment: diff --git a/packages/syncfusion_flutter_datagrid/CHANGELOG.md b/packages/syncfusion_flutter_datagrid/CHANGELOG.md index 67d7b7e00..ccf8f1bc0 100644 --- a/packages/syncfusion_flutter_datagrid/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid/CHANGELOG.md @@ -2,10 +2,52 @@ **Features** +* Implemented support for scrolling to a specific position upon initial loading. +* Implemented support to filter the custom data types using the `strongDataType` behavior. + +**Bugs** + +* The auto row height is now working properly for the data rows when the sort and filter icons show in the column header. + +**Breaking changes** + +* Removed the deprecated `showFilterIconOnHover` property. The `showColumnHeaderIconOnHover` property is commonly used for showing both sort and filter icons. + +## [24.2.8] - 02/27/2024 + +**Bugs** + +* The app now responds to the back key in Android when the datagrid is in focus. +* The datagrid rows are now properly refreshed when adjusting the rows per page using the checkbox column. + +## [24.2.7] - 02/20/2024 + +**Bugs** + +* The current page index now updates correctly when changing the number of rows per page at runtime. +* The datagrid now correctly resets the frozen column count during runtime. +* The filtering popup menu now displays correctly with Material 2. + +## [24.1.47] - 01/23/2024 + +**Bugs** + +* The null exception will no longer be thrown when interactions are performed in the caption summary row with the interaction callbacks. + +## [24.1.43] - 12/27/2023 + +**Bugs** + +* Now, the DataGrid properly refreshes the cell value when updated, triggered by calling `notifyDataSourceListeners` alongside the DataPager. + +## [24.1.41] - 12/18/2023 + +**Features** + * Provided support for grouping a collection of records based on particular categories, offering various customization options. This includes single and multiple column grouping, along with options to customize appearances and interactively expand and collapse a group. * Provided support for retrieving the start and end index of visible rows and columns based on the row region in the DataGrid. -## [24.1.41] - 12/06/2023 +## [23.2.7] - 12/06/2023 **Bugs** diff --git a/packages/syncfusion_flutter_datagrid/README.md b/packages/syncfusion_flutter_datagrid/README.md index c437106d2..c095b2491 100644 --- a/packages/syncfusion_flutter_datagrid/README.md +++ b/packages/syncfusion_flutter_datagrid/README.md @@ -75,6 +75,10 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ![First row and column are frozen in flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-freeze-panes.gif) +**Grouping** - Allows users to organize and categorize data based on specific criteria, facilitating efficient interaction with summarized information. + +**Column drag and drop** - Interactively customize the arrangement of columns by dragging and dropping them, enhancing user flexibility and personalization in organizing and viewing data. + **Swiping** - Swipe a row right to left or left to right for custom actions such as deleting, editing, and so on. When the user swipes a row, the row will be moved and the swipe view will show the custom actions. **Pull to refresh** - Allows users to refresh data when the DataGrid is pulled down. diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart index 61dcf9d87..fbf0b8b39 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart @@ -386,11 +386,6 @@ class DataGridConfiguration { /// Defaults to true. bool showVerticalScrollbar = true; - /// Decides whether the filter icon should be shown when hovering the header cells. - /// - /// Defaults to false. - bool showFilterIconOnHover = false; - /// A boolean flag to indicate whether column dragging is allowed or not. bool allowColumnsDragging = false; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart index 3e86c2300..77c4f8bba 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart @@ -9,6 +9,7 @@ import '../../grid_common/scroll_axis.dart'; import '../../grid_common/visible_line_info.dart'; import '../grouping/grouping.dart'; import '../runtime/column.dart'; +import '../runtime/generator.dart'; import '../sfdatagrid.dart'; import 'datagrid_configuration.dart'; import 'enums.dart'; @@ -655,6 +656,8 @@ int? _getCompareValue(Object? cellValue, Object? filterValue) { return cellValue.compareTo(filterValue as num); } else if (cellValue is DateTime) { return cellValue.compareTo(filterValue as DateTime); + } else if (cellValue is Comparable) { + return cellValue.compareTo(filterValue); } return null; } @@ -1225,3 +1228,21 @@ double resolveScrollOffsetToPosition( return measuredScrollOffset; } + +/// This method helps to resolve getting the column for the tap interaction callbacks. +GridColumn? getGridColumn( + DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { + GridColumn? column = dataCell.gridColumn; + if (dataCell.dataRow != null && + (dataCell.dataRow!.rowType == RowType.captionSummaryCoveredRow || + dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow)) { + final int startIndex = dataGridConfiguration.showCheckboxColumn ? 1 : 0; + + column = dataGridConfiguration.columns.firstWhereOrNull( + (GridColumn element) => + element.actualWidth > 0 && + element.visible && + dataGridConfiguration.columns.indexOf(element) >= startIndex); + } + return column; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart index 0a3436ea9..6e2afbe7b 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart @@ -76,7 +76,7 @@ class GridCellTextFieldRenderer final DataRowBase? dataRow = dataCell.dataRow; if (dataRow != null && dataRow.isSelectedRow) { return dataRow.isHoveredRow - ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle + ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle! : TextStyle( fontFamily: 'Roboto', fontWeight: FontWeight.w400, @@ -85,7 +85,7 @@ class GridCellTextFieldRenderer .withOpacity(0.87)); } else { return dataRow!.isHoveredRow - ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle + ? dataGridConfiguration.dataGridThemeHelper!.rowHoverTextStyle! : TextStyle( fontFamily: 'Roboto', fontWeight: FontWeight.w400, @@ -399,7 +399,7 @@ class GridCaptionSummaryCellRenderer key: dataCell.key!, dataCell: dataCell, backgroundColor: - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.04), + dataGridConfiguration.dataGridThemeHelper!.captionSummaryRowColor, dataGridStateDetails: _dataGridStateDetails, isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, child: label, @@ -445,7 +445,7 @@ class GridIndentCellRenderer backgroundColor: dataCell.rowIndex >= grid_helper .resolveStartIndexBasedOnPosition(dataGridConfiguration) - ? dataGridConfiguration.dataGridThemeHelper!.indentColumnColor + ? dataGridConfiguration.dataGridThemeHelper!.indentColumnColor! : Colors.transparent, dataGridStateDetails: _dataGridStateDetails, isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart index 214f770bc..4c78c754f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart @@ -13,6 +13,7 @@ import '../../grid_common/visible_line_info.dart'; import '../helper/callbackargs.dart'; import '../helper/datagrid_configuration.dart'; import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/datagrid_helper.dart'; import '../helper/enums.dart'; import '../sfdatagrid.dart'; import 'generator.dart'; @@ -1014,7 +1015,8 @@ class ColumnSizer { column, grid_helper.getHeaderIndex(_dataGridStateDetails!()), column.columnName, - textStyle); + textStyle, + isHeaderCell: true); } /// Calculates the height of the cell based on the [DataGridCell.value]. @@ -1056,7 +1058,10 @@ class ColumnSizer { double autoFitHeight = 0.0; if (dataGridConfiguration.stackedHeaderRows.isNotEmpty && rowIndex <= dataGridConfiguration.stackedHeaderRows.length - 1) { - return dataGridConfiguration.headerRowHeight; + // We did not provide support to automatically calculate the height of a + // stacked header based on the content. + // So we have returned the default header row height . + return 56.0; } if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration)) { return dataGridConfiguration.footerHeight; @@ -1106,10 +1111,12 @@ class ColumnSizer { } double _measureCellHeight( - GridColumn column, int rowIndex, Object? cellValue, TextStyle textStyle) { + GridColumn column, int rowIndex, Object? cellValue, TextStyle textStyle, + {bool isHeaderCell = false}) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); - final int columnIndex = dataGridConfiguration.columns.indexOf(column); + final int columnIndex = resolveToScrollColumnIndex( + dataGridConfiguration, dataGridConfiguration.columns.indexOf(column)); double columnWidth = !column.visible || column.width == 0.0 ? dataGridConfiguration.defaultColumnWidth : dataGridConfiguration.container.columnWidths[columnIndex]; @@ -1122,14 +1129,16 @@ class ColumnSizer { final double horizontalPadding = column.autoFitPadding.horizontal; - // Removed the padding and gridline stroke width from the column width to - // measure the accurate height for the cell content. - double iconsWidth = _getSortIconWidth(column) + _getFilterIconWidth(column); - - if (iconsWidth > 0) { - iconsWidth += iconsOuterPadding.horizontal; + double iconsWidth = 0.0; + if (isHeaderCell) { + iconsWidth = _getSortIconWidth(column) + _getFilterIconWidth(column); + if (iconsWidth > 0) { + iconsWidth += iconsOuterPadding.horizontal; + } } + // Removed the padding and gridline stroke width from the column width to + // measure the accurate height for the cell content. columnWidth -= iconsWidth + horizontalPadding + strokeWidth; return _calculateTextSize( @@ -1165,7 +1174,7 @@ class ColumnSizer { required DataGridConfiguration dataGridConfiguration, required GridColumn column}) { final double strokeWidth = - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth; + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!; final GridLinesVisibility gridLinesVisibility = rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration) @@ -1358,12 +1367,15 @@ class ColumnResizeController { _resizingLine = _getResizingLine(dx, canAllowBuffer); + int startColumnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + // Need to disable column resizing for the indent columns. + if (dataGridConfiguration.source.groupedColumns.isNotEmpty) { + startColumnIndex += dataGridConfiguration.source.groupedColumns.length; + } + if (_resizingLine != null && - _resizingLine!.lineIndex >= - grid_helper.resolveToStartColumnIndex(dataGridConfiguration) && - (resizingDataCell?.cellType != CellType.indentCell && - resizingDataCell!.columnIndex > - dataGridConfiguration.source.groupedColumns.length)) { + _resizingLine!.lineIndex >= startColumnIndex) { // To ensure the resizing line for the stacked header row. if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { if (!_canAllowResizing(dx, resizingDataCell, @@ -1594,7 +1606,7 @@ class ColumnResizeController { // To remove the half of stroke width to show the indicator to the // center of grid line. indicatorLeft -= dataGridConfiguration - .dataGridThemeHelper!.columnResizeIndicatorStrokeWidth / + .dataGridThemeHelper!.columnResizeIndicatorStrokeWidth! / 2; return indicatorLeft; @@ -1988,26 +2000,6 @@ class DataGridFilterHelper { .fontSize! + 38; - /// Provides the icon color. - Color get iconColor => - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.6); - - /// Provides the disable icon color. - Color get disableIconColor => - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.38); - - /// Provides the border color. - Color get borderColor => - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.12); - - /// Provides the background color. - Color get backgroundColor => - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.001); - - /// Provides the text color. - Color get textColor => - _dataGridStateDetails().colorScheme!.onSurface.withOpacity(0.89); - /// Provides the primary color. Color get primaryColor => _dataGridStateDetails().colorScheme!.primary; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart index 066b03885..44dd7e104 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart @@ -972,20 +972,20 @@ class RowGenerator { return BoxDecoration( border: BorderDirectional( - top: canDrawTopFrozenBorder && themeData.frozenPaneElevation <= 0.0 + top: canDrawTopFrozenBorder && themeData.frozenPaneElevation! <= 0.0 ? BorderSide( - color: themeData.frozenPaneLineColor, - width: themeData.frozenPaneLineWidth) + color: themeData.frozenPaneLineColor!, + width: themeData.frozenPaneLineWidth!) : BorderSide.none, bottom: canDrawHorizontalBorder ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) + color: themeData.gridLineColor!, + width: themeData.gridLineStrokeWidth!) : BorderSide.none, end: canDrawVerticalBorder ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) + color: themeData.gridLineColor!, + width: themeData.gridLineStrokeWidth!) : BorderSide.none, ), ); @@ -1198,8 +1198,9 @@ class RowGenerator { spanDataRow.rowLevel); _updataSpannedRow(spanDataRow, displayElement, columnSpan); if (rowData.level > displayElement.level) { - for (int i = displayElement.level; i < rowData.level; i++) + for (int i = displayElement.level; i < rowData.level; i++) { spanDataRow.visibleColumns[i].columnIndex = -1; + } } spanDataRow.ensureIndentCell(visibleColumns); } else { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart index e3d2a8225..d33b86eae 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart @@ -25,7 +25,7 @@ class SelectionManagerBase extends ChangeNotifier { /// Processes the selection operation when [SfDataGrid] receives raw keyboard /// event. - void handleKeyEvent(RawKeyEvent keyEvent) {} + void handleKeyEvent(KeyEvent keyEvent) {} /// Called when the [SfDataGrid.selectionMode] is changed at run time. void onGridSelectionModeChanged() {} @@ -902,7 +902,7 @@ class RowSelectionManager extends SelectionManagerBase { //KeyNavigation @override - Future handleKeyEvent(RawKeyEvent keyEvent) async { + Future handleKeyEvent(KeyEvent keyEvent) async { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); if (dataGridConfiguration.currentCell.isEditing && @@ -965,7 +965,7 @@ class RowSelectionManager extends SelectionManagerBase { } if (keyEvent.logicalKey == LogicalKeyboardKey.keyA) { - if (keyEvent.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { _processSelectedAll(); } } @@ -998,7 +998,7 @@ class RowSelectionManager extends SelectionManagerBase { } void _processEndKey( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, KeyEvent keyEvent) { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int lastCellIndex = selection_helper.getLastCellIndex(dataGridConfiguration); @@ -1012,8 +1012,8 @@ class RowSelectionManager extends SelectionManagerBase { } if ((dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) && + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && keyEvent.logicalKey != LogicalKeyboardKey.arrowRight) { final int lastRowIndex = selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); @@ -1028,7 +1028,7 @@ class RowSelectionManager extends SelectionManagerBase { } void _processHomeKey( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, KeyEvent keyEvent) { final CurrentCellManager currentCell = dataGridConfiguration.currentCell; final int firstCellIndex = selection_helper.getFirstCellIndex(dataGridConfiguration); @@ -1042,8 +1042,8 @@ class RowSelectionManager extends SelectionManagerBase { } if ((dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) && + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && keyEvent.logicalKey != LogicalKeyboardKey.arrowLeft) { final int firstRowIndex = selection_helper.getFirstNavigatingRowIndex(dataGridConfiguration); @@ -1080,7 +1080,7 @@ class RowSelectionManager extends SelectionManagerBase { } } - void _processKeyDown(RawKeyEvent keyEvent) { + void _processKeyDown(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; @@ -1100,21 +1100,21 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) { + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { selection_helper.scrollInViewFromTop(dataGridConfiguration, needToScrollToMaxExtent: true); _processSelectionAndCurrentCell( dataGridConfiguration, RowColumnIndex(lastRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed); } else { _processSelectionAndCurrentCell( dataGridConfiguration, RowColumnIndex(nextRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed); } } - void _processKeyUp(RawKeyEvent keyEvent) { + void _processKeyUp(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; @@ -1128,24 +1128,24 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) { + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { final int firstRowIndex = selection_helper.getFirstRowIndex(dataGridConfiguration); selection_helper.scrollInViewFromDown(dataGridConfiguration, needToScrollToMinExtent: true); _processSelectionAndCurrentCell( dataGridConfiguration, RowColumnIndex(firstRowIndex, columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed); } else { _processSelectionAndCurrentCell( dataGridConfiguration, RowColumnIndex(previousRowIndex, columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); + isShiftKeyPressed: HardwareKeyboard.instance.isShiftPressed); } } void _processKeyRight( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, KeyEvent keyEvent) { if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { return; } @@ -1165,8 +1165,8 @@ class RowSelectionManager extends SelectionManagerBase { // Need to get previous column index only if the control key is // pressed in RTL mode since it will perform the home key event. if ((dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) && + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && dataGridConfiguration.textDirection == TextDirection.rtl) { nextCellIndex = selection_helper.getPreviousColumnIndex( dataGridConfiguration, currentCell.columnIndex); @@ -1183,8 +1183,8 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) { + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { if (dataGridConfiguration.textDirection == TextDirection.rtl) { _processHomeKey(dataGridConfiguration, keyEvent); } else { @@ -1201,7 +1201,7 @@ class RowSelectionManager extends SelectionManagerBase { } void _processKeyLeft( - DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + DataGridConfiguration dataGridConfiguration, KeyEvent keyEvent) { if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { return; } @@ -1219,8 +1219,8 @@ class RowSelectionManager extends SelectionManagerBase { // Need to get next column index only if the control key is // pressed in RTL mode since it will perform the end key event. if ((dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) && + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) && dataGridConfiguration.textDirection == TextDirection.rtl) { previousCellIndex = selection_helper.getNextColumnIndex( dataGridConfiguration, currentCell.columnIndex); @@ -1237,8 +1237,8 @@ class RowSelectionManager extends SelectionManagerBase { } if (dataGridConfiguration.isMacPlatform - ? keyEvent.isMetaPressed - : keyEvent.isControlPressed) { + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed) { if (dataGridConfiguration.textDirection == TextDirection.rtl) { _processEndKey(dataGridConfiguration, keyEvent); } else { @@ -1254,7 +1254,7 @@ class RowSelectionManager extends SelectionManagerBase { } } - void _processKeyTab(RawKeyEvent keyEvent) { + void _processKeyTab(KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails!(); final CurrentCellManager currentCell = dataGridConfiguration.currentCell; @@ -1283,7 +1283,7 @@ class RowSelectionManager extends SelectionManagerBase { dataGridConfiguration.container.extentWidth > dataGridConfiguration.viewWidth; - if (keyEvent.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { if (currentCell.columnIndex == firstCellIndex && currentCell.rowIndex == firstRowIndex) { return; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart index 3c6848efe..178987031 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart @@ -480,8 +480,6 @@ class SfDataGrid extends StatefulWidget { this.checkboxShape, this.showHorizontalScrollbar = true, this.showVerticalScrollbar = true, - @Deprecated('use SfDataGrid.showColumnHeaderIconOnHover instead') - this.showFilterIconOnHover = false, this.allowColumnsDragging = false, this.onColumnDragging, this.columnDragFeedbackBuilder, @@ -1631,12 +1629,6 @@ class SfDataGrid extends StatefulWidget { /// Defaults to true. final bool showVerticalScrollbar; - /// Decides whether the filter icon should be shown when hovering the header cells. - /// - /// Defaults to false. - @Deprecated('use SfDataGrid.showColumnHeaderIconOnHover instead') - final bool showFilterIconOnHover; - /// Decides whether the column can be dragged and dropped to the required position. /// /// Columns will not be automatically reordered from one position to another position. You must use the [SfDataGrid.onColumnDragging] callback. For this, you must maintain the columns in variable and assign to [SfDataGrid.columns] property. Then, you can reorder a column in the collection inside the `setState` method through [SfDataGrid.onColumnDragging] callback. @@ -1804,8 +1796,7 @@ class SfDataGridState extends State } if (canRefreshView) { - _dataGridThemeHelper = DataGridThemeHelper( - _dataGridThemeData, _dataGridConfiguration.colorScheme); + _dataGridThemeHelper = DataGridThemeHelper(_dataGridThemeData!, context); _dataGridConfiguration.dataGridThemeHelper = _dataGridThemeHelper; _updateDecoration(); _container.refreshViewStyle(); @@ -2091,6 +2082,17 @@ class SfDataGridState extends State return; } + final DataCellBase? dataCell = dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); + + if (dataCell == null) { + return; + } + + // Need to update the data source; otherwise, the `effectiveRows` collection will not + // be updated based on the changes made in the `rows` collection on the user's end. + updateDataSource(_source!); + // Issue: // FLUT-7231-Editing of filtered data when paging is applied does not work properly. // @@ -2107,13 +2109,6 @@ class SfDataGridState extends State dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( _dataGridConfiguration, dataRow.dataGridRow!); - final DataCellBase? dataCell = dataRow.visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); - - if (dataCell == null) { - return; - } - if (mounted) { setState(() { _refreshCell(dataCell); @@ -2198,8 +2193,9 @@ class SfDataGridState extends State _initializeDataGridDataSource(); _dataGridConfiguration.source = _source!; - if (widget.selectionMode != SelectionMode.none) + if (widget.selectionMode != SelectionMode.none) { selection_manager.removeUnWantedDataGridRows(_dataGridConfiguration); + } if (widget.selectionMode != SelectionMode.none && widget.navigationMode == GridNavigationMode.cell && _rowSelectionManager != null) { @@ -2621,11 +2617,6 @@ class SfDataGridState extends State ..checkboxShape = widget.checkboxShape ..showHorizontalScrollbar = widget.showHorizontalScrollbar ..showVerticalScrollbar = widget.showVerticalScrollbar - // We have used the deprecated property to support the old behavior. - // So, we have suppressed the deprecated member use warning. - // We will remove the deprecated property once the old behavior is removed. - // ignore: deprecated_member_use_from_same_package - ..showFilterIconOnHover = widget.showFilterIconOnHover ..allowColumnsDragging = widget.allowColumnsDragging ..onColumnDragging = widget.onColumnDragging ..columnDragFeedbackBuilder = widget.columnDragFeedbackBuilder @@ -2662,8 +2653,19 @@ class SfDataGridState extends State // collection at every time. So, we have used the column's length to check // whether the columns collection is changed or not at runtime // and update required changes in the data grid based on it. + + // Issue: + // Bug 870510: The DataGrid did not update properly when changing rowsPerPage + //along with the DataPager and Checkbox column. + // + // Fix: + // we've taken out the count of checkboxes from the collection of internal columns. + // This is done to prevent updating paginated rows before initializing the number of rows per page. + final int columnLength = _dataGridConfiguration.showCheckboxColumn + ? _columns!.length - 1 + : _columns!.length; final bool isColumnsCollectionChanged = - _columns!.length != widget.columns.length; + columnLength != widget.columns.length; final bool isSelectionManagerChanged = oldWidget.selectionManager != widget.selectionManager || oldWidget.selectionMode != widget.selectionMode; @@ -2704,7 +2706,7 @@ class SfDataGridState extends State widget.rowsPerPage != oldWidget.rowsPerPage; // To apply filtering to the runtime changes of columns. final bool canApplyFiltering = - isColumnsChanged && _columns!.length != widget.columns.length; + isColumnsChanged && isColumnsCollectionChanged; final bool isFilteringChanged = oldWidget.allowFiltering != widget.allowFiltering; @@ -3137,7 +3139,7 @@ class SfDataGridState extends State void _updateDecoration() { final BorderSide borderSide = BorderSide( color: _dataGridConfiguration - .dataGridThemeHelper!.currentCellStyle.borderColor); + .dataGridThemeHelper!.currentCellStyle!.borderColor); final BoxDecoration decoration = BoxDecoration( border: Border( bottom: borderSide, @@ -4969,136 +4971,168 @@ class DataGridThemeHelper { /// To Do DataGridThemeHelper( - SfDataGridThemeData? dataGridThemeData, ColorScheme? colorScheme) { - brightness = dataGridThemeData!.brightness ?? colorScheme!.brightness; + SfDataGridThemeData dataGridThemeData, BuildContext context) { + final ThemeData theme = Theme.of(context); + final SfDataGridThemeData defaults = SfDataGridTheme.of(context); + final _SfDataGridThemeDataM2 sfDataGridThemeDataM2 = + _SfDataGridThemeDataM2(context, dataGridThemeData); + final _SfDataGridThemeDataM3 sfDataGridThemeDataM3 = + _SfDataGridThemeDataM3(context, dataGridThemeData); + final SfDataGridThemeData effectiveDataGridThemeData = + theme.useMaterial3 ? sfDataGridThemeDataM3 : sfDataGridThemeDataM2; headerColor = - dataGridThemeData.headerColor ?? Colors.transparent.withOpacity(0.0001); - gridLineColor = dataGridThemeData.gridLineColor ?? - colorScheme!.onSurface.withOpacity(0.12); - gridLineStrokeWidth = dataGridThemeData.gridLineStrokeWidth ?? 1; - frozenPaneElevation = dataGridThemeData.frozenPaneElevation ?? 5; - frozenPaneLineWidth = dataGridThemeData.frozenPaneLineWidth ?? 2; - selectionColor = dataGridThemeData.selectionColor ?? - colorScheme!.onSurface.withOpacity(0.08); - headerHoverColor = dataGridThemeData.headerHoverColor ?? - colorScheme!.onSurface.withOpacity(0.04); - rowHoverColor = dataGridThemeData.rowHoverColor ?? - colorScheme!.onSurface.withOpacity(0.04); - sortIconColor = dataGridThemeData.sortIconColor ?? - colorScheme!.onSurface.withOpacity(0.6); - frozenPaneLineColor = dataGridThemeData.frozenPaneLineColor ?? - colorScheme!.onSurface.withOpacity(0.38); - columnResizeIndicatorColor = - dataGridThemeData.columnResizeIndicatorColor ?? colorScheme!.primary; + defaults.headerColor ?? effectiveDataGridThemeData.headerColor; + gridLineColor = + defaults.gridLineColor ?? effectiveDataGridThemeData.gridLineColor; + gridLineStrokeWidth = defaults.gridLineStrokeWidth ?? + effectiveDataGridThemeData.gridLineStrokeWidth; + frozenPaneElevation = defaults.frozenPaneElevation ?? + effectiveDataGridThemeData.frozenPaneElevation; + frozenPaneLineWidth = defaults.frozenPaneLineWidth ?? + effectiveDataGridThemeData.frozenPaneLineWidth; + selectionColor = + defaults.selectionColor ?? effectiveDataGridThemeData.selectionColor; + headerHoverColor = defaults.headerHoverColor ?? + effectiveDataGridThemeData.headerHoverColor; + rowHoverColor = + defaults.rowHoverColor ?? effectiveDataGridThemeData.rowHoverColor; + sortIconColor = + defaults.sortIconColor ?? effectiveDataGridThemeData.sortIconColor; + frozenPaneLineColor = defaults.frozenPaneLineColor ?? + effectiveDataGridThemeData.frozenPaneLineColor; + columnResizeIndicatorColor = defaults.columnResizeIndicatorColor ?? + effectiveDataGridThemeData.columnResizeIndicatorColor; columnResizeIndicatorStrokeWidth = - dataGridThemeData.columnResizeIndicatorStrokeWidth ?? 2; - currentCellStyle = dataGridThemeData.currentCellStyle ?? - DataGridCurrentCellStyle( - borderColor: colorScheme!.onSurface.withOpacity(0.26), - borderWidth: 1.0); - rowHoverTextStyle = dataGridThemeData.rowHoverTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onSurface.withOpacity(0.87)); - sortIcon = dataGridThemeData.sortIcon; - filterIcon = dataGridThemeData.filterIcon; - filterIconColor = dataGridThemeData.filterIconColor; - filterIconHoverColor = dataGridThemeData.filterIconHoverColor; - sortOrderNumberColor = dataGridThemeData.sortOrderNumberColor; - sortOrderNumberBackgroundColor = - dataGridThemeData.sortOrderNumberBackgroundColor; - _filterPopupTextStyle = TextStyle( - fontSize: 14.0, - color: colorScheme!.onSurface.withOpacity(0.89), - fontFamily: 'Roboto', - fontWeight: FontWeight.normal); - - _filterPopupDisabledTextStyle = TextStyle( - fontSize: 14.0, - color: colorScheme.onSurface.withOpacity(0.38), - fontFamily: 'Roboto', - fontWeight: FontWeight.normal); - - filterPopupTextStyle = dataGridThemeData.filterPopupTextStyle != null - ? _filterPopupTextStyle.merge(dataGridThemeData.filterPopupTextStyle) - : _filterPopupTextStyle; - filterPopupDisabledTextStyle = - dataGridThemeData.filterPopupDisabledTextStyle != null - ? _filterPopupDisabledTextStyle - .merge(dataGridThemeData.filterPopupDisabledTextStyle) - : _filterPopupDisabledTextStyle; - columnDragIndicatorColor = - dataGridThemeData.columnDragIndicatorColor ?? colorScheme.primary; - columnDragIndicatorStrokeWidth = - dataGridThemeData.columnDragIndicatorStrokeWidth ?? 2; - groupExpanderIcon = dataGridThemeData.groupExpanderIcon; - indentColumnWidth = dataGridThemeData.indentColumnWidth; - indentColumnColor = dataGridThemeData.indentColumnColor ?? - colorScheme.onSurface.withOpacity(0.04); + defaults.columnResizeIndicatorStrokeWidth ?? + effectiveDataGridThemeData.columnResizeIndicatorStrokeWidth; + currentCellStyle = defaults.currentCellStyle ?? + effectiveDataGridThemeData.currentCellStyle; + + rowHoverTextStyle = defaults.rowHoverTextStyle ?? + effectiveDataGridThemeData.rowHoverTextStyle; + sortIcon = defaults.sortIcon ?? effectiveDataGridThemeData.sortIcon; + filterIcon = defaults.filterIcon ?? effectiveDataGridThemeData.filterIcon; + filterIconColor = + defaults.filterIconColor ?? effectiveDataGridThemeData.filterIconColor; + filterIconHoverColor = defaults.filterIconHoverColor ?? + effectiveDataGridThemeData.filterIconHoverColor; + sortOrderNumberColor = defaults.sortOrderNumberColor ?? + effectiveDataGridThemeData.sortOrderNumberColor; + sortOrderNumberBackgroundColor = defaults.sortOrderNumberBackgroundColor ?? + effectiveDataGridThemeData.sortOrderNumberBackgroundColor; + _filterPopupTextStyle = effectiveDataGridThemeData.filterPopupTextStyle; + + _filterPopupDisabledTextStyle = + effectiveDataGridThemeData.filterPopupDisabledTextStyle; + + filterPopupTextStyle = _filterPopupTextStyle!.merge( + defaults.filterPopupTextStyle ?? + effectiveDataGridThemeData.filterPopupTextStyle); + filterPopupDisabledTextStyle = _filterPopupDisabledTextStyle! + .merge(defaults.filterPopupDisabledTextStyle); + + columnDragIndicatorColor = defaults.columnDragIndicatorColor ?? + effectiveDataGridThemeData.columnDragIndicatorColor; + columnDragIndicatorStrokeWidth = defaults.columnDragIndicatorStrokeWidth ?? + effectiveDataGridThemeData.columnDragIndicatorStrokeWidth; + groupExpanderIcon = defaults.groupExpanderIcon; + indentColumnWidth = defaults.indentColumnWidth ?? + effectiveDataGridThemeData.indentColumnWidth!; + indentColumnColor = defaults.indentColumnColor ?? + effectiveDataGridThemeData.indentColumnColor; + filterPopupIconColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupIconColor + : sfDataGridThemeDataM2.filterPopupIconColor; + filterPopupDisableIconColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupDisableIconColor + : sfDataGridThemeDataM2.filterPopupDisableIconColor; + filterPopupBorderColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupBorderColor + : sfDataGridThemeDataM2.filterPopupBorderColor; + filterPopupBackgroundColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupBackgroundColor + : sfDataGridThemeDataM2.filterPopupBackgroundColor; + filterPopupTextColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupTextColor + : sfDataGridThemeDataM2.filterPopupTextColor; + filterPopupOuterColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.filterPopupBackgroundColor + : theme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF303030); + feedBackWidgetColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.feedBackWidgetColor + : theme.brightness == Brightness.light + ? const Color(0xFFFAFAFA) + : const Color(0xFF303030); + captionSummaryRowColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.captionSummaryRowColor + : sfDataGridThemeDataM2.captionSummaryRowColor; + captionSummaryRowHoverColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.captionSummaryRowHoverColor + : sfDataGridThemeDataM2.captionSummaryRowHoverColor; + tableSummaryRowColor = theme.useMaterial3 + ? sfDataGridThemeDataM3.tableSummaryRowColor + : sfDataGridThemeDataM2.tableSummaryRowColor; } - ///To Do - late Brightness brightness; - // ignore: public_member_api_docs - late Color headerColor; + late final Color? headerColor; /// To do - late Color gridLineColor; + late final Color? gridLineColor; /// To do - late double gridLineStrokeWidth; + late final double? gridLineStrokeWidth; /// To do - late Color selectionColor; + late final Color? selectionColor; /// To do - late DataGridCurrentCellStyle currentCellStyle; + late final DataGridCurrentCellStyle? currentCellStyle; /// To do - late double frozenPaneLineWidth; + late final double? frozenPaneLineWidth; /// To do - late Color frozenPaneLineColor; + late final Color? frozenPaneLineColor; /// To do - late Color sortIconColor; + late final Color? sortIconColor; /// To do - late Color headerHoverColor; + late final Color? headerHoverColor; /// To do - late double frozenPaneElevation; + late final double? frozenPaneElevation; /// To do - late Color columnResizeIndicatorColor; + late final Color? columnResizeIndicatorColor; /// To do - late double columnResizeIndicatorStrokeWidth; + late final double? columnResizeIndicatorStrokeWidth; /// To do - late Color rowHoverColor; + late final Color? rowHoverColor; /// To do - late TextStyle rowHoverTextStyle; + late final TextStyle? rowHoverTextStyle; /// To do - late Widget? sortIcon; + late final Widget? sortIcon; /// The icon to indicate the filtering applied in column. /// @@ -5155,54 +5189,367 @@ class DataGridThemeHelper { /// ); /// } /// ``` - late Widget? filterIcon; + late final Widget? filterIcon; /// The color of the filter icon which indicates whether the column is filtered or not. /// /// This is not applicable when `filterIcon` property is set. /// This applies the color to default filter icon only. - late Color? filterIconColor; + late final Color? filterIconColor; /// The color for the filter icon when a pointer is hovering over it. /// /// This is not applicable when `filterIcon` property is set. /// This applies the color to default filter icon only. - late Color? filterIconHoverColor; + late final Color? filterIconHoverColor; /// The color of the number displayed when the order of the sorting is shown. - late Color? sortOrderNumberColor; + late final Color? sortOrderNumberColor; /// Creates a copy of this theme but with the given fields replaced with the new values. - late Color? sortOrderNumberBackgroundColor; + late final Color? sortOrderNumberBackgroundColor; /// The [TextStyle] of the options in filter popup menu except the items which are already selected. - late TextStyle? filterPopupTextStyle; + late final TextStyle? filterPopupTextStyle; /// The [TextStyle] of the disabled options in filter popup menu. - late TextStyle? filterPopupDisabledTextStyle; + late final TextStyle? filterPopupDisabledTextStyle; /// Default filter popup menu textStyle - late TextStyle _filterPopupTextStyle; + late final TextStyle? _filterPopupTextStyle; /// Default filter popup menu disable textStyle - late TextStyle _filterPopupDisabledTextStyle; + late final TextStyle? _filterPopupDisabledTextStyle; ///To do - late Color columnDragIndicatorColor; + late final Color? columnDragIndicatorColor; ///To do - late double columnDragIndicatorStrokeWidth; + late final double? columnDragIndicatorStrokeWidth; /// This icon indicates the expand-collapse state of a group in a caption summary row. /// /// It will be displayed only if the [SfDataGrid.autoExpandCollapseGroup] property is set to true. - late Widget? groupExpanderIcon; + late final Widget? groupExpanderIcon; /// The width of an indent column. /// /// Defaults to 40.0. - late double indentColumnWidth; + late final double indentColumnWidth; /// The color of an indent column. - late Color indentColumnColor; + late final Color? indentColumnColor; + + /// Provides the icon color. + late final Color? filterPopupIconColor; + + /// Provides the disable icon color. + late final Color? filterPopupDisableIconColor; + + /// Provides the border color. + late final Color? filterPopupBorderColor; + + /// Provides the background color. + late final Color? filterPopupBackgroundColor; + + /// Provides the text color. + late final Color? filterPopupTextColor; + + /// Provides the text color. + late final Color? filterPopupOuterColor; + + /// Provides the feedBack widgetcolor. + late final Color? feedBackWidgetColor; + + /// Provides the caption summaryrow color. + late final Color captionSummaryRowColor; + + /// Provides the caption summaryrow hover color. + late final Color captionSummaryRowHoverColor; + + /// Provides the table summary row color. + late final Color tableSummaryRowColor; +} + +/// +/// Defines the theme data for the [SfDataGrid] widget for Material 3 design. +/// +class _SfDataGridThemeDataM3 extends SfDataGridThemeData { + /// Constructs the [_SfDataGridThemeDataM3] + _SfDataGridThemeDataM3(this.context, this.dataGridThemeData); + + //// The build context. + final BuildContext context; + + /// The data grid theme data. + final SfDataGridThemeData dataGridThemeData; + + /// The color scheme derived from the current theme context. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color get gridLineColor => colorScheme.outlineVariant; + + @override + double get gridLineStrokeWidth => 1; + + @override + Color get selectionColor => colorScheme.primaryContainer; + + @override + DataGridCurrentCellStyle get currentCellStyle => DataGridCurrentCellStyle( + borderColor: colorScheme.primary, borderWidth: 2.0); + + @override + double get frozenPaneLineWidth => 2; + + @override + Color get frozenPaneLineColor => colorScheme.outline; + + @override + Color get sortIconColor => colorScheme.onSurfaceVariant; + + @override + Color get headerHoverColor => colorScheme.primary.withOpacity(0.08); + + @override + double get frozenPaneElevation => 5; + + @override + Color get headerColor => colorScheme.surface.withOpacity(0.0001); + + @override + Color get columnResizeIndicatorColor => colorScheme.primary; + + @override + double get columnResizeIndicatorStrokeWidth => 2; + + @override + Color get rowHoverColor => colorScheme.primary.withOpacity(0.08); + + @override + TextStyle get rowHoverTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface.withOpacity(0.87)); + + @override + Widget? get sortIcon => dataGridThemeData.sortIcon; + + @override + Widget? get filterIcon => dataGridThemeData.filterIcon; + + @override + Color? get filterIconColor => dataGridThemeData.filterIconColor; + + @override + Color? get filterIconHoverColor => dataGridThemeData.filterIconHoverColor; + + @override + Color? get sortOrderNumberColor => dataGridThemeData.sortOrderNumberColor; + + @override + Color? get sortOrderNumberBackgroundColor => + dataGridThemeData.sortOrderNumberBackgroundColor; + + @override + TextStyle get filterPopupTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface.withOpacity(0.89), + fontFamily: 'Roboto', + fontWeight: FontWeight.normal); + + @override + TextStyle get filterPopupDisabledTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface.withOpacity(0.38), + fontFamily: 'Roboto', + fontWeight: FontWeight.normal); + + @override + Color get columnDragIndicatorColor => colorScheme.primary; + + @override + double get columnDragIndicatorStrokeWidth => 2; + + @override + Widget? get groupExpanderIcon => dataGridThemeData.groupExpanderIcon; + + @override + double get indentColumnWidth => 40; + + @override + Color get indentColumnColor => colorScheme.primary.withOpacity(0.08); + + /// Provides the icon color. + Color get filterPopupIconColor => colorScheme.onSurfaceVariant; + + /// Provides the disable icon color. + Color get filterPopupDisableIconColor => + colorScheme.onSurface.withOpacity(0.38); + + /// Provides the border color. + Color get filterPopupBorderColor => colorScheme.onSurface.withOpacity(0.12); + + /// Provides the background color. + Color get filterPopupBackgroundColor => + colorScheme.brightness == Brightness.light + ? const Color(0xFFEEE8F4) + : const Color(0xFF302D38); + + /// Provides the text color. + Color get filterPopupTextColor => const Color(0xFF49454F); + + /// Provides the feedBack widgetcolor. + Color get feedBackWidgetColor => colorScheme.surface; + + /// Provides the caption summaryrow color. + Color get captionSummaryRowColor => colorScheme.primary.withOpacity(0.08); + + /// Provides the caption summaryrow hover color. + Color get captionSummaryRowHoverColor => + colorScheme.primary.withOpacity(0.12); + + /// Provides the table summary row color. + Color get tableSummaryRowColor => colorScheme.primary.withOpacity(0.08); +} + +/// +/// Defines the theme data for the [SfDataGrid] widget for Material 2 design. +/// +class _SfDataGridThemeDataM2 extends SfDataGridThemeData { + /// Constructs the [_SfDataGridThemeDataM2] + _SfDataGridThemeDataM2(this.context, this.dataGridThemeData); + + /// The build context. + final BuildContext context; + + /// The data grid theme data. + final SfDataGridThemeData dataGridThemeData; + + /// The color scheme derived from the current theme context. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color get gridLineColor => colorScheme.onSurface.withOpacity(0.12); + + @override + double get gridLineStrokeWidth => 1; + + @override + Color get selectionColor => colorScheme.onSurface.withOpacity(0.08); + + @override + DataGridCurrentCellStyle get currentCellStyle => DataGridCurrentCellStyle( + borderColor: colorScheme.onSurface.withOpacity(0.26), borderWidth: 1.0); + + @override + double get frozenPaneLineWidth => 2; + + @override + Color get frozenPaneLineColor => colorScheme.onSurface.withOpacity(0.38); + + @override + Color get sortIconColor => colorScheme.onSurface.withOpacity(0.6); + + @override + Color get headerHoverColor => colorScheme.onSurface.withOpacity(0.04); + + @override + double get frozenPaneElevation => 5; + + @override + Color get headerColor => colorScheme.surface.withOpacity(0.0001); + + @override + Color get columnResizeIndicatorColor => colorScheme.primary; + + @override + double get columnResizeIndicatorStrokeWidth => 2; + + @override + Color get rowHoverColor => colorScheme.onSurface.withOpacity(0.04); + + @override + TextStyle get rowHoverTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface.withOpacity(0.87)); + + @override + Widget? get sortIcon => dataGridThemeData.sortIcon; + + @override + Widget? get filterIcon => dataGridThemeData.filterIcon; + + @override + Color? get filterIconColor => dataGridThemeData.filterIconColor; + + @override + Color? get filterIconHoverColor => dataGridThemeData.filterIconHoverColor; + + @override + Color? get sortOrderNumberColor => dataGridThemeData.sortOrderNumberColor; + + @override + Color? get sortOrderNumberBackgroundColor => + dataGridThemeData.sortOrderNumberBackgroundColor; + + @override + TextStyle get filterPopupTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface.withOpacity(0.89), + fontFamily: 'Roboto', + fontWeight: FontWeight.normal); + + @override + TextStyle get filterPopupDisabledTextStyle => TextStyle( + fontSize: 14.0, + color: colorScheme.onSurface.withOpacity(0.38), + fontFamily: 'Roboto', + fontWeight: FontWeight.normal); + + @override + Color? get columnDragIndicatorColor => colorScheme.primary; + + @override + double get columnDragIndicatorStrokeWidth => 2; + + @override + Widget? get groupExpanderIcon => dataGridThemeData.groupExpanderIcon; + + @override + double get indentColumnWidth => 40; + + @override + Color get indentColumnColor => colorScheme.onSurface.withOpacity(0.04); + + /// Provides the icon color. + Color get filterPopupIconColor => colorScheme.onSurface.withOpacity(0.6); + + /// Provides the disable icon color. + Color get filterPopupDisableIconColor => + colorScheme.onSurface.withOpacity(0.38); + + /// Provides the border color. + Color get filterPopupBorderColor => colorScheme.onSurface.withOpacity(0.12); + + /// Provides the background color. + Color get filterPopupBackgroundColor => + colorScheme.onSurface.withOpacity(0.001); + + /// Provides the text color. + Color get filterPopupTextColor => colorScheme.onSurface.withOpacity(0.89); + + /// Provides the caption summaryrow color. + Color get captionSummaryRowColor => colorScheme.onSurface.withOpacity(0.04); + + /// Provides the caption summary row hover color. + Color get captionSummaryRowHoverColor => + colorScheme.onSurface.withOpacity(0.08); + + /// Provides the table summary row color. + Color get tableSummaryRowColor => Colors.transparent; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart index 4cf9e03f1..e4b0959ca 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart @@ -372,15 +372,13 @@ class _GridHeaderCellState extends State { width: widget.dataCell.gridColumn!.actualWidth, height: dataGridConfiguration.headerRowHeight, decoration: BoxDecoration( - color: dataGridConfiguration.colorScheme!.brightness == - Brightness.light - ? const Color(0xFFFAFAFA) - : const Color(0xFF303030), + color: dataGridConfiguration + .dataGridThemeHelper!.feedBackWidgetColor, border: Border.all( color: dataGridConfiguration - .dataGridThemeHelper!.gridLineColor, + .dataGridThemeHelper!.gridLineColor!, width: dataGridConfiguration - .dataGridThemeHelper!.gridLineStrokeWidth)), + .dataGridThemeHelper!.gridLineStrokeWidth!)), child: widget.child); } @@ -492,7 +490,7 @@ class _GridHeaderCellState extends State { if (sortIconWidth > 0 && availableWidth > sortIconWidth + filterIconWidth) { _sortIconColor = - dataGridConfiguration.dataGridThemeHelper!.sortIconColor; + dataGridConfiguration.dataGridThemeHelper!.sortIconColor!; _sortIcon = dataGridConfiguration.dataGridThemeHelper!.sortIcon; if (_sortDirection != null) { @@ -545,12 +543,6 @@ class _GridHeaderCellState extends State { .dataGridFilterHelper!.isFilterPopupMenuShowing || isFilteredColumn || isSortedColumn; - } else if (!dataGridConfiguration.showColumnHeaderIconOnHover && - dataGridConfiguration.showFilterIconOnHover && - dataGridConfiguration.isDesktop) { - return isHovered || - dataGridConfiguration - .dataGridFilterHelper!.isFilterPopupMenuShowing; } else { return true; } @@ -571,17 +563,7 @@ class _GridHeaderCellState extends State { children['filterIcon']!, ], ) - : (dataGridConfiguration.showFilterIconOnHover && - !dataGridConfiguration.showColumnHeaderIconOnHover) - ? Row( - children: [ - if (children.containsKey('sortIcon')) - children['sortIcon']!, - if (children.containsKey('sortNumber')) - children['sortNumber']!, - ], - ) - : const SizedBox(), + : const SizedBox(), ), ); } @@ -617,9 +599,7 @@ class _GridHeaderCellState extends State { Flexible( child: Container(child: child), ), - if (isColumnHeaderIconVisible || - (dataGridConfiguration.showFilterIconOnHover && - !dataGridConfiguration.showColumnHeaderIconOnHover)) + if (isColumnHeaderIconVisible) Container( padding: dataGridConfiguration.columnSizer.iconsOuterPadding, child: Row( @@ -637,9 +617,7 @@ class _GridHeaderCellState extends State { } else { headerCell = Row( children: [ - if (isColumnHeaderIconVisible || - (dataGridConfiguration.showFilterIconOnHover && - !dataGridConfiguration.showColumnHeaderIconOnHover)) + if (isColumnHeaderIconVisible) Container( padding: dataGridConfiguration.columnSizer.iconsOuterPadding, child: Row( @@ -897,11 +875,10 @@ class _FilterIcon extends StatelessWidget { final Offset newOffset = renderBox.globalToLocal(details.globalPosition); final Size viewSize = renderBox.size; showMenu( + surfaceTintColor: Colors.transparent, context: context, color: - dataGridConfiguration.colorScheme!.brightness == Brightness.light - ? const Color(0xFFFAFAFA) - : const Color(0xFF303030), + dataGridConfiguration.dataGridThemeHelper!.filterPopupOuterColor, constraints: const BoxConstraints(maxWidth: 274.0), position: RelativeRect.fromSize(newOffset & Size.zero, viewSize), items: >[ @@ -962,7 +939,7 @@ class _FilterIcon extends StatelessWidget { : (dataGridConfiguration .dataGridThemeHelper!.filterIconColor ?? dataGridConfiguration - .dataGridFilterHelper!.iconColor), + .dataGridThemeHelper!.filterPopupIconColor!), filterIcon: dataGridConfiguration.dataGridThemeHelper!.filterIcon, gridColumnName: column.columnName, @@ -976,7 +953,7 @@ class _FilterIcon extends StatelessWidget { : (dataGridConfiguration .dataGridThemeHelper!.filterIconColor ?? dataGridConfiguration - .dataGridFilterHelper!.iconColor), + .dataGridThemeHelper!.filterPopupIconColor!), filterIcon: dataGridConfiguration.dataGridThemeHelper!.filterIcon, gridColumnName: column.columnName, @@ -1083,6 +1060,8 @@ class _FilterPopupState extends State<_FilterPopup> { late bool isAdvancedFilter; late DataGridFilterHelper filterHelper; + + late DataGridThemeHelper dataGridThemeHelper; @override void initState() { super.initState(); @@ -1097,11 +1076,17 @@ class _FilterPopupState extends State<_FilterPopup> { replacement: Material( child: _buildPopupView(), ), - child: SafeArea( - child: Theme( - data: - ThemeData(colorScheme: widget.dataGridConfiguration.colorScheme), + child: Theme( + data: ThemeData( + colorScheme: widget.dataGridConfiguration.colorScheme, + // Issue: FLUT-869897-The color of the filter pop-up menu was not working properly + // on the Mobile platform when using the Material 2. + // + // Fix: We have to set the useMaterial3 property to the theme data to resolve the above issue. + useMaterial3: Theme.of(context).useMaterial3), + child: SafeArea( child: Scaffold( + backgroundColor: dataGridThemeHelper.filterPopupOuterColor, appBar: buildAppBar(context), resizeToAvoidBottomInset: true, body: _buildPopupView(), @@ -1126,6 +1111,7 @@ class _FilterPopupState extends State<_FilterPopup> { void _initializeFilterProperties() { isMobile = !widget.dataGridConfiguration.isDesktop; filterHelper = widget.dataGridConfiguration.dataGridFilterHelper!; + dataGridThemeHelper = widget.dataGridConfiguration.dataGridThemeHelper!; filterHelper.filterFrom = filterHelper.getFilterForm(widget.column); isAdvancedFilter = filterHelper.filterFrom == FilteredFrom.advancedFilter; filterHelper.checkboxFilterHelper.textController.clear(); @@ -1168,12 +1154,15 @@ class _FilterPopupState extends State<_FilterPopup> { elevation: 0.0, bottom: PreferredSize( preferredSize: const Size.fromHeight(1.0), - child: Container(height: 1.0, color: filterHelper.borderColor)), - backgroundColor: filterHelper.backgroundColor, + child: Container( + height: 1.0, + color: dataGridThemeHelper.filterPopupBorderColor)), + backgroundColor: dataGridThemeHelper.filterPopupBackgroundColor, leading: IconButton( key: const ValueKey('datagrid_filtering_cancelFilter_icon'), onPressed: closePage, - icon: Icon(Icons.close, size: 22.0, color: filterHelper.iconColor)), + icon: Icon(Icons.close, + size: 22.0, color: dataGridThemeHelper.filterPopupIconColor)), centerTitle: false, titleSpacing: 0, title: Text( @@ -1197,7 +1186,7 @@ class _FilterPopupState extends State<_FilterPopup> { } Widget _buildPopupView() { - final Color iconColor = filterHelper.iconColor; + final Color iconColor = dataGridThemeHelper.filterPopupIconColor!; final AdvancedFilterType filterType = filterHelper.advancedFilterHelper.advancedFilterType; final SfLocalizations localizations = @@ -1241,7 +1230,7 @@ class _FilterPopupState extends State<_FilterPopup> { key: const ValueKey('datagrid_filtering_scrollView'), child: Container( width: isMobile ? null : 274.0, - color: filterHelper.backgroundColor, + color: dataGridThemeHelper.filterPopupBackgroundColor, child: Column( children: [ if (canShowSortingOptions) @@ -1256,7 +1245,7 @@ class _FilterPopupState extends State<_FilterPopup> { fontPackage: 'syncfusion_flutter_datagrid'), color: isSortAscendingEnabled ? iconColor - : filterHelper.disableIconColor, + : dataGridThemeHelper.filterPopupDisableIconColor, size: filterHelper.textStyle.fontSize! + 10, ), prefixPadding: EdgeInsets.only( @@ -1284,7 +1273,7 @@ class _FilterPopupState extends State<_FilterPopup> { fontPackage: 'syncfusion_flutter_datagrid'), color: isSortDescendingEnabled ? iconColor - : filterHelper.disableIconColor, + : dataGridThemeHelper.filterPopupDisableIconColor, size: filterHelper.textStyle.fontSize! + 10, ), prefixPadding: EdgeInsets.only( @@ -1320,7 +1309,7 @@ class _FilterPopupState extends State<_FilterPopup> { size: filterHelper.textStyle.fontSize! + 8, color: isClearFilterEnabled ? iconColor - : filterHelper.disableIconColor), + : dataGridThemeHelper.filterPopupDisableIconColor), prefixPadding: EdgeInsets.only( left: 4.0, right: filterHelper.textStyle.fontSize!, @@ -1775,6 +1764,8 @@ class _CheckboxFilterMenu extends StatelessWidget { Widget _buildSearchBox(Color onSurface, BuildContext context) { final DataGridFilterHelper helper = dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; void onSearchboxSubmitted(String value) { if (filterHelper.items.isNotEmpty) { @@ -1798,7 +1789,8 @@ class _CheckboxFilterMenu extends StatelessWidget { onSubmitted: onSearchboxSubmitted, decoration: InputDecoration( enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor)), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!)), suffixIcon: Visibility( visible: filterHelper.textController.text.isEmpty, replacement: IconButton( @@ -1812,10 +1804,11 @@ class _CheckboxFilterMenu extends StatelessWidget { filterHelper.textController.clear(); onHandleSearchTextFieldChanged(''); }, - icon: Icon(Icons.close, color: helper.iconColor)), + icon: Icon(Icons.close, + color: dataGridThemeHelper.filterPopupIconColor)), child: Icon(Icons.search, size: helper.textStyle.fontSize! + 8, - color: helper.iconColor)), + color: dataGridThemeHelper.filterPopupIconColor)), contentPadding: isMobile ? const EdgeInsets.all(16.0) : const EdgeInsets.all(8.0), @@ -1997,6 +1990,9 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { final DataGridFilterHelper helper = dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + void setValue(Object? value) { if (isTopButton) { filterHelper.filterValue1 = value; @@ -2030,6 +2026,7 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { Widget buildDropdownFormField() { return DropdownButtonHideUnderline( child: DropdownButtonFormField( + dropdownColor: dataGridThemeHelper.filterPopupOuterColor, key: isTopButton ? const ValueKey( 'datagrid_filtering_filterValue_first_button') @@ -2037,14 +2034,17 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { 'datagrid_filtering_filterValue_second_button'), decoration: InputDecoration( enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!), ), contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), border: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor))), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!))), icon: Icon(Icons.keyboard_arrow_down, - size: helper.textStyle.fontSize! + 8, color: helper.iconColor), + size: helper.textStyle.fontSize! + 8, + color: dataGridThemeHelper.filterPopupIconColor), isExpanded: true, value: isTopButton ? filterHelper.filterValue1 @@ -2081,7 +2081,8 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { }, decoration: InputDecoration( enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor)), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!)), contentPadding: isMobile ? const EdgeInsets.all(16.0) : const EdgeInsets.all(8.0), @@ -2099,6 +2100,9 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { final DataGridFilterHelper helper = dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; + void handleChanged(String? value) { if (isFirstButton) { filterHelper.filterType1 = value; @@ -2146,6 +2150,7 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { return DropdownButtonHideUnderline( child: DropdownButtonFormField( + dropdownColor: dataGridThemeHelper.filterPopupOuterColor, key: isFirstButton ? const ValueKey( 'datagrid_filtering_filterType_first_button') @@ -2153,13 +2158,16 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { 'datagrid_filtering_filterType_second_button'), decoration: InputDecoration( enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor)), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!)), contentPadding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0), border: OutlineInputBorder( - borderSide: BorderSide(color: helper.borderColor))), + borderSide: BorderSide( + color: dataGridThemeHelper.filterPopupBorderColor!))), icon: Icon(Icons.keyboard_arrow_down, - size: helper.textStyle.fontSize! + 8, color: helper.iconColor), + size: helper.textStyle.fontSize! + 8, + color: dataGridThemeHelper.filterPopupIconColor), isExpanded: true, value: isFirstButton ? filterHelper.filterType1 : filterHelper.filterType2, @@ -2176,6 +2184,8 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { Widget? _getTrailingWidget(BuildContext context, bool isFirstButton) { final DataGridFilterHelper helper = dataGridConfiguration.dataGridFilterHelper!; + final DataGridThemeHelper dataGridThemeHelper = + dataGridConfiguration.dataGridThemeHelper!; if (filterHelper.advancedFilterType == AdvancedFilterType.numeric) { return null; @@ -2240,7 +2250,9 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { final bool isSelected = isFirstButton ? filterHelper.isCaseSensitive1 : filterHelper.isCaseSensitive2; - return isSelected ? helper.primaryColor : helper.iconColor; + return isSelected + ? helper.primaryColor + : dataGridThemeHelper.filterPopupIconColor!; } bool canEnableButton() { @@ -2301,9 +2313,9 @@ class _AdvancedFilterPopupMenu extends StatelessWidget { BorderDirectional _getCellBorder( DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { final Color borderColor = - dataGridConfiguration.dataGridThemeHelper!.gridLineColor; + dataGridConfiguration.dataGridThemeHelper!.gridLineColor!; final double borderWidth = - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth; + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!; final int rowIndex = (dataCell.rowSpan > 0) ? dataCell.rowIndex - dataCell.rowSpan @@ -2508,13 +2520,13 @@ BorderDirectional _getCellBorder( columnIndex; final bool isFrozenPaneElevationApplied = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation > 0.0; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation! > 0.0; final Color frozenPaneLineColor = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineColor; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineColor!; final double frozenPaneLineWidth = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineWidth; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneLineWidth!; final bool canDrawIndentRightBorder = canDrawVerticalBorder && (dataGridConfiguration.source.groupedColumns.isNotEmpty && @@ -2544,9 +2556,9 @@ BorderDirectional _getCellBorder( !canSkipLeftColumnDragAndDropIndicator) { return BorderSide( width: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorStrokeWidth, + .dataGridThemeHelper!.columnDragIndicatorStrokeWidth!, color: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorColor); + .dataGridThemeHelper!.columnDragIndicatorColor!); } if (canDrawLeftFrozenBorder && !isStackedHeaderCell && @@ -2565,9 +2577,9 @@ BorderDirectional _getCellBorder( !canSkipLeftColumnDragAndDropIndicator) { return BorderSide( width: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorStrokeWidth, + .dataGridThemeHelper!.columnDragIndicatorStrokeWidth!, color: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorColor); + .dataGridThemeHelper!.columnDragIndicatorColor!); } else { return BorderSide.none; } @@ -2611,9 +2623,9 @@ BorderDirectional _getCellBorder( !canSkipRightColumnDragAndDropIndicator) { return BorderSide( width: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorStrokeWidth, + .dataGridThemeHelper!.columnDragIndicatorStrokeWidth!, color: dataGridConfiguration - .dataGridThemeHelper!.columnDragIndicatorColor); + .dataGridThemeHelper!.columnDragIndicatorColor!); } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && !canDrawRightFrozenBorder && !isCaptionSummaryCell && @@ -2682,9 +2694,9 @@ Widget _wrapInsideCellContainer( required Color backgroundColor, required Widget child}) { final Color color = - dataGridConfiguration.dataGridThemeHelper!.currentCellStyle.borderColor; + dataGridConfiguration.dataGridThemeHelper!.currentCellStyle!.borderColor; final double borderWidth = - dataGridConfiguration.dataGridThemeHelper!.currentCellStyle.borderWidth; + dataGridConfiguration.dataGridThemeHelper!.currentCellStyle!.borderWidth; Border getBorder() { final bool isCurrentCell = dataCell.isCurrentCell; @@ -2829,9 +2841,23 @@ Future _handleOnTapUp( } if (!isSecondaryTapDown && dataGridConfiguration.onCellTap != null) { + // Issue: + // FLUT-865739-A null exception occurred when expanding the group alongside the onCellTap callback. + // + // Reason for the issue: The gridcolumn is null when the tapping the caption summary cell. + // + // Fix: We need to check the gridcolumn is null or not before invoking the onCellDoubleTap callback. + // For the caption summary cell, we need to get the first visible column from the columns collection. + final GridColumn? column = + grid_helper.getGridColumn(dataGridConfiguration, dataCell); + + if (column == null) { + return; + } + final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, + column: column, globalPosition: tapDownDetails != null ? tapDownDetails.globalPosition : tapUpDetails!.globalPosition, @@ -2899,9 +2925,16 @@ Future _handleOnDoubleTap( } if (dataGridConfiguration.onCellDoubleTap != null) { + final GridColumn? column = + grid_helper.getGridColumn(dataGridConfiguration, dataCell); + + if (column == null) { + return; + } + final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!); + column: column); dataGridConfiguration.onCellDoubleTap!(details); } @@ -2933,9 +2966,16 @@ Future _handleOnSecondaryTapUp( } if (dataGridConfiguration.onCellSecondaryTap != null) { + final GridColumn? column = + grid_helper.getGridColumn(dataGridConfiguration, dataCell); + + if (column == null) { + return; + } + final DataGridCellTapDetails details = DataGridCellTapDetails( rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, + column: column, globalPosition: tapUpDetails.globalPosition, localPosition: tapUpDetails.localPosition, kind: kind); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart index 6511fa1ee..676195d13 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart @@ -601,13 +601,7 @@ class RenderVirtualizingCellsWidget extends RenderBox } Rect _getRowRect(DataGridConfiguration dataGridConfiguration, Offset offset, - {bool isHoveredLayer = false, double indentColumn = -1}) { - final double indentColumnWidth = - dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth; - final int indentColumnCount = - dataGridConfiguration.source.groupedColumns.length; - final bool directionRTL = - dataGridConfiguration.textDirection == TextDirection.rtl; + {bool isHoveredLayer = false, bool isCurrentRowBorderLayer = false}) { bool needToSetMaxConstraint() => dataGridConfiguration.container.extentWidth < dataGridConfiguration.viewWidth && @@ -620,74 +614,101 @@ class RenderVirtualizingCellsWidget extends RenderBox dataGridConfiguration.viewWidth, dataGridConfiguration.footerHeight); } else { - return Rect.fromLTWH( - needToSetMaxConstraint() - ? constraints.maxWidth - - min(dataGridConfiguration.container.extentWidth, + late int indentCount; + final bool isRTLDirection = + dataGridConfiguration.textDirection == TextDirection.rtl; + + // Current row border is applicable for both data row and caption summary covered row + // So, considered the row type with this `isCurrentRowBorderLayer` flag itself. + isCurrentRowBorderLayer = isCurrentRowBorderLayer && + (dataRow.rowType == RowType.dataRow || + dataRow.rowType == RowType.captionSummaryCoveredRow); + + if (isCurrentRowBorderLayer && + dataRow.rowType == RowType.captionSummaryCoveredRow) { + final dynamic rowData = dataRow.rowData; + if (rowData != null && rowData is Group) { + indentCount = rowData.level - 1; + } + } else { + indentCount = dataGridConfiguration.source.groupedColumns.length; + } + + final double indentColumnsWidth = + dataGridConfiguration.dataGridThemeHelper!.indentColumnWidth * + indentCount; + + double dx = needToSetMaxConstraint() + ? constraints.maxWidth - + min(dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth) - + dataGridConfiguration.container.horizontalOffset + : dataGridConfiguration.container.horizontalOffset; + + if (isCurrentRowBorderLayer || + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + if (indentColumnsWidth > 0) { + if (!isRTLDirection) { + dx = max(dx, indentColumnsWidth); + } + } + } + + double width = needToSetMaxConstraint() + ? constraints.maxWidth + : min(dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth); + + if (isCurrentRowBorderLayer || + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + if (indentColumnsWidth > 0) { + // Need to remove the left side view space from the width in RTL mode when + // the datagrid is unscrollable. + if (needToSetMaxConstraint() && + (isHoveredLayer && dataRow.rowType == RowType.dataRow)) { + width -= dataGridConfiguration.viewWidth - + dataGridConfiguration.container.extentWidth; + } + + double scrollOffset = 0.0; + if (isRTLDirection) { + if (dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth) { + scrollOffset = (dataGridConfiguration.container.extentWidth - dataGridConfiguration.viewWidth) - - (offset.dx + dataGridConfiguration.container.horizontalOffset) - : dataGridConfiguration.source.groupedColumns.isNotEmpty && - !directionRTL - ? (isHoveredLayer && - dataRow.rowType == RowType.captionSummaryCoveredRow) - ? offset.dx + - dataGridConfiguration.container.horizontalOffset - : indentColumn >= 0 - ? max(offset.dx + indentColumn, - dataGridConfiguration.container.horizontalOffset) - : (dataRow.rowType == - RowType.captionSummaryCoveredRow || - dataRow.rowType == RowType.dataRow) - ? max( - offset.dx + - (indentColumnWidth * indentColumnCount), - dataGridConfiguration - .container.horizontalOffset) - : offset.dx + - dataGridConfiguration - .container.horizontalOffset - : offset.dx + - dataGridConfiguration.container.horizontalOffset, - offset.dy, - needToSetMaxConstraint() - ? constraints.maxWidth - indentColumn - : dataGridConfiguration.source.groupedColumns.isNotEmpty && - directionRTL - ? (isHoveredLayer && - dataRow.rowType == RowType.captionSummaryCoveredRow) - ? min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth) - : indentColumn >= 0 - ? min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth) - - (offset.dx + indentColumn) - : min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth) - - min( - indentColumnWidth * indentColumnCount, - dataGridConfiguration - .container.horizontalOffset) - : dataRow.rowType == RowType.dataRow - ? min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth) - - (indentColumnWidth * indentColumnCount) - : min(dataGridConfiguration.container.extentWidth, - dataGridConfiguration.viewWidth), - (isHoveredLayer && - dataRow.isHoveredRow && - (dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridConfiguration.gridLinesVisibility == - GridLinesVisibility.both)) - ? constraints.maxHeight - - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth - : constraints.maxHeight); + dataGridConfiguration.container.horizontalOffset; + } + } else { + scrollOffset = dataGridConfiguration.container.horizontalOffset; + } + + if (scrollOffset < indentColumnsWidth) { + width -= indentColumnsWidth - scrollOffset; + } + } + } + + final double height = (isHoveredLayer && + dataRow.isHoveredRow && + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both)) + ? constraints.maxHeight - + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth! + : constraints.maxHeight; + + return Rect.fromLTWH(offset.dx + dx, offset.dy, width, height); } } void _drawRowBackground(DataGridConfiguration dataGridConfiguration, PaintingContext context, Offset offset) { - final Rect rect = _getRowRect(dataGridConfiguration, offset); + // Need to consider the selected row as hovered layer to avoid apply + // selection for the indent columns. + final Rect rect = _getRowRect(dataGridConfiguration, offset, + isHoveredLayer: dataRow.isSelectedRow); + Color? backgroundColor; Color getDefaultRowBackgroundColor() { @@ -745,12 +766,13 @@ class RenderVirtualizingCellsWidget extends RenderBox dataRow.rowType == RowType.stackedHeaderRow) { backgroundColor = dataGridConfiguration.dataGridThemeHelper!.headerColor; - drawSpannedRowBackgroundColor(backgroundColor); + drawSpannedRowBackgroundColor(backgroundColor!); } else if (dataRow.rowType == RowType.footerRow) { backgroundColor = getDefaultRowBackgroundColor(); } else if (dataRow.rowType == RowType.tableSummaryRow || dataRow.rowType == RowType.tableSummaryCoveredRow) { - backgroundColor = dataRow.tableSummaryRow?.color; + backgroundColor = dataRow.tableSummaryRow?.color ?? + dataGridConfiguration.dataGridThemeHelper!.tableSummaryRowColor; } else if (dataRow.rowType == RowType.captionSummaryCoveredRow) { backgroundColor = getDefaultRowBackgroundColor(); } else { @@ -780,23 +802,6 @@ class RenderVirtualizingCellsWidget extends RenderBox ((dataGridConfiguration.navigationMode == GridNavigationMode.cell && dataRow.rowType == RowType.captionSummaryCoveredRow) || dataGridConfiguration.navigationMode == GridNavigationMode.row)) { - double indentColumnWidths = 0; - final List groupedColumns = - dataGridConfiguration.source.groupedColumns; - final DataGridThemeHelper? themeHelper = - dataGridConfiguration.dataGridThemeHelper; - - if (groupedColumns.isNotEmpty) { - final int currentRowIndex = dataGridConfiguration.currentCell.rowIndex; - final dynamic groupItem = getGroupElement(dataGridConfiguration, - resolveStartRecordIndex(dataGridConfiguration, currentRowIndex)); - - final double indentColumnWidth = themeHelper!.indentColumnWidth; - final int level = - groupItem is Group ? groupItem.level - 1 : groupedColumns.length; - - indentColumnWidths = indentColumnWidth * level; - } bool needToSetMaxConstraint() => dataGridConfiguration.container.extentWidth < dataGridConfiguration.viewWidth && @@ -804,11 +809,12 @@ class RenderVirtualizingCellsWidget extends RenderBox const double stokeWidth = 1; final int origin = (stokeWidth / 2 + - dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth) + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!) .ceil(); final Rect rowRect = _getRowRect(dataGridConfiguration, offset, - indentColumn: indentColumnWidths); + isCurrentRowBorderLayer: true); + final double maxWidth = needToSetMaxConstraint() ? rowRect.width - rowRect.left : rowRect.right - rowRect.left; @@ -1244,10 +1250,10 @@ class RenderVirtualizingCellsWidget extends RenderBox dataGridConfiguration.highlightRowOnHover && dataRow.isHoveredRow) { dataGridConfiguration.gridPaint?.color = - dataGridConfiguration.dataGridThemeHelper!.rowHoverColor; + dataGridConfiguration.dataGridThemeHelper!.rowHoverColor!; if (dataRow.rowType == RowType.captionSummaryCoveredRow) { - dataGridConfiguration.gridPaint?.color = - dataGridConfiguration.colorScheme!.onSurface.withOpacity(0.08); + dataGridConfiguration.gridPaint?.color = dataGridConfiguration + .dataGridThemeHelper!.captionSummaryRowHoverColor; } context.canvas.drawRect( _getRowRect(dataGridConfiguration, offset, isHoveredLayer: true), @@ -1284,8 +1290,9 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowType == RowType.tableSummaryRow || dataRow.rowType == RowType.tableSummaryCoveredRow) { final BorderSide border = BorderSide( - width: dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth, - color: dataGridConfiguration.dataGridThemeHelper!.gridLineColor); + width: + dataGridConfiguration.dataGridThemeHelper!.gridLineStrokeWidth!, + color: dataGridConfiguration.dataGridThemeHelper!.gridLineColor!); if (dataGridConfiguration.textDirection == TextDirection.ltr) { paintBorder(context.canvas, getRowRect(), right: border); @@ -1389,6 +1396,7 @@ class RenderVirtualizingCellsWidget extends RenderBox if (dataRow.rowType == RowType.stackedHeaderRow || dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.captionSummaryCoveredRow || dataRow.rowType == RowType.tableSummaryCoveredRow) { dataCell = dataRow.visibleColumns.firstWhereOrNull((DataCellBase dataCell) { @@ -1414,11 +1422,21 @@ class RenderVirtualizingCellsWidget extends RenderBox if (_dataCellBase != null && _longPressStartDetails != null && dataGridConfiguration.onCellLongPress != null) { + // Issue: FLUT-865739-A null exception occurred when expanding the group alongside the onCellTap callback. + // Reason: The grid column is null when the row type is caption summary covered row. + // Fix: Check the grid column is null or not before invoking the onCellLongPress callback. + final GridColumn? column = + grid_helper.getGridColumn(dataGridConfiguration, _dataCellBase!); + + if (column == null) { + return; + } + final DataGridCellLongPressDetails longPressDetails = DataGridCellLongPressDetails( rowColumnIndex: RowColumnIndex( _dataCellBase!.rowIndex, _dataCellBase!.columnIndex), - column: _dataCellBase!.gridColumn!, + column: column, globalPosition: _longPressStartDetails!.globalPosition, localPosition: _longPressStartDetails!.localPosition); dataGridConfiguration.onCellLongPress!(longPressDetails); @@ -2126,7 +2144,7 @@ class RenderGridCell extends RenderBox final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); dataGridConfiguration.gridPaint!.color = - dataGridConfiguration.dataGridThemeHelper!.headerHoverColor; + dataGridConfiguration.dataGridThemeHelper!.headerHoverColor!; final Rect cellRect = Rect.fromLTRB(0, 0, constraints.maxWidth, constraints.maxHeight); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart index f24e8660b..66727e288 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart @@ -137,12 +137,13 @@ class _ScrollViewWidgetState extends State { void _setHorizontalOffset() { if (_container.needToSetHorizontalOffset) { - _container.horizontalOffset = _horizontalController!.hasClients - ? _horizontalController!.offset - : 0.0; + if (_horizontalController!.hasClients) { + _container.horizontalOffset = _horizontalController!.offset; + } else if (_horizontalController!.initialScrollOffset <= 0) { + _container.horizontalOffset = 0.0; + } _container.scrollColumns.markDirty(); } - _container.needToSetHorizontalOffset = false; } @@ -377,6 +378,10 @@ class _ScrollViewWidgetState extends State { _horizontalController!.hasClients && dataGridConfiguration.shrinkWrapColumns) { return -_horizontalController!.offset; + } else if (!_horizontalController!.hasClients && + _horizontalController!.initialScrollOffset > 0) { + final double maxScrollExtent = _container.extentWidth - _width; + return -(maxScrollExtent - _container.horizontalOffset); } return 0.0; } else { @@ -488,17 +493,17 @@ class _ScrollViewWidgetState extends State { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; final DataGridThemeHelper? dataGridThemeHelper = dataGridConfiguration.dataGridThemeHelper; - if (dataGridThemeHelper!.frozenPaneElevation <= 0.0 || + if (dataGridThemeHelper!.frozenPaneElevation! <= 0.0 || dataGridConfiguration.columns.isEmpty || effectiveRows(dataGridConfiguration.source).isEmpty) { return; } final Color frozenLineColorWithoutOpacity = - dataGridThemeHelper.frozenPaneLineColor; + dataGridThemeHelper.frozenPaneLineColor!; final Color frozenLineColorWithOpacity = - dataGridThemeHelper.frozenPaneLineColor.withOpacity(0.14); + dataGridThemeHelper.frozenPaneLineColor!.withOpacity(0.14); void drawElevation({ EdgeInsets? margin, @@ -574,7 +579,7 @@ class _ScrollViewWidgetState extends State { // In 4.0 pixels, 1.0 pixel defines the size of the container and // 3.0 pixels defines the amount of spreadRadius. final double margin = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation + 4.0; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation! + 4.0; final int frozenColumnIndex = grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); final int footerFrozenColumnIndex = @@ -602,7 +607,7 @@ class _ScrollViewWidgetState extends State { dataGridConfiguration.container.horizontalOffset > 0) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; frozenLineColor = frozenLineColorWithOpacity; } if (dataGridConfiguration.textDirection == TextDirection.rtl && @@ -612,7 +617,7 @@ class _ScrollViewWidgetState extends State { dataGridConfiguration.container.horizontalOffset) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; frozenLineColor = frozenLineColorWithOpacity; } @@ -634,7 +639,7 @@ class _ScrollViewWidgetState extends State { !_canDisableHorizontalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 3.0; double blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; Color frozenLineColor = frozenLineColorWithOpacity; final double top = getTopPosition(columnHeaderRow, footerFrozenColumnIndex); @@ -680,7 +685,7 @@ class _ScrollViewWidgetState extends State { if (dataGridConfiguration.container.verticalOffset > 0) { spreadRadiusValue = 3.0; blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; frozenLineColor = frozenLineColorWithOpacity; } @@ -700,7 +705,7 @@ class _ScrollViewWidgetState extends State { !_canDisableVerticalScrolling(dataGridConfiguration)) { double spreadRadiusValue = 3.0; double blurRadiusValue = - dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation; + dataGridConfiguration.dataGridThemeHelper!.frozenPaneElevation!; Color frozenLineColor = frozenLineColorWithOpacity; final double bottom = columnHeaderRow.getRowHeight( footerFrozenRowIndex, dataGridConfiguration.container.rowCount); @@ -858,8 +863,7 @@ class _ScrollViewWidgetState extends State { // -------------------------------------------------------------------------- - KeyEventResult _handleKeyOperation( - FocusNode focusNode, RawKeyEvent keyEvent) { + KeyEventResult _handleKeyOperation(FocusNode focusNode, KeyEvent keyEvent) { final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; final CurrentCellManager currentCell = dataGridConfiguration.currentCell; // To handle group expansion and collapse the group. @@ -897,19 +901,19 @@ class _ScrollViewWidgetState extends State { } void processKeys() { - if (keyEvent.runtimeType == RawKeyDownEvent) { + if (keyEvent.runtimeType == KeyDownEvent) { _rowSelectionManager.handleKeyEvent(keyEvent); - if (keyEvent.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { dataGridConfiguration.isControlKeyPressed = true; } - if (keyEvent.isMetaPressed) { + if (HardwareKeyboard.instance.isMetaPressed) { dataGridConfiguration.isCommandKeyPressed = true; } - if (keyEvent.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { dataGridConfiguration.isShiftKeyPressed = true; } } - if (keyEvent.runtimeType == RawKeyUpEvent) { + if (keyEvent.runtimeType == KeyUpEvent) { if (keyEvent.logicalKey == LogicalKeyboardKey.controlLeft || keyEvent.logicalKey == LogicalKeyboardKey.controlRight) { dataGridConfiguration.isControlKeyPressed = false; @@ -936,7 +940,7 @@ class _ScrollViewWidgetState extends State { currentCell.rowIndex == rowIndex) || (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); - if (keyEvent.isShiftPressed) { + if (HardwareKeyboard.instance.isShiftPressed) { final int firstRowIndex = selection_helper.getFirstRowIndex(dataGridConfiguration); final int firstCellIndex = @@ -965,6 +969,8 @@ class _ScrollViewWidgetState extends State { if (keyEvent.logicalKey == LogicalKeyboardKey.tab && needToMoveFocus() != KeyEventResult.handled) { return KeyEventResult.ignored; + } else if (keyEvent.logicalKey == LogicalKeyboardKey.goBack) { + return KeyEventResult.ignored; } processKeys(); @@ -972,8 +978,8 @@ class _ScrollViewWidgetState extends State { (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight || keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft)) { if (dataGridConfiguration.isMacPlatform - ? !keyEvent.isMetaPressed - : !keyEvent.isControlPressed) { + ? !HardwareKeyboard.instance.isMetaPressed + : !HardwareKeyboard.instance.isControlPressed) { expandCollapseGroup(); } } @@ -1071,10 +1077,21 @@ class _ScrollViewWidgetState extends State { _updateAxis(); + if (_verticalController!.initialScrollOffset > 0 && + !_container.isPreGenerator) { + _container.verticalOffset = _verticalController!.initialScrollOffset; + } + //if shrink wrap width is not set we update the column size after updated the axis because we get the constraint max width if (!_dataGridConfiguration.shrinkWrapColumns) { _updateColumnSizer(); } + + if (_horizontalController!.initialScrollOffset > 0 && + !_container.isPreGenerator) { + _container.horizontalOffset = + _horizontalController!.initialScrollOffset; + } _container ..setRowHeights(initialLoading: true) ..needToRefreshColumn = true; @@ -1147,7 +1164,7 @@ class _ScrollViewWidgetState extends State { return Focus( key: _dataGridConfiguration.dataGridKey, focusNode: _dataGridFocusNode, - onKey: _handleKeyOperation, + onKeyEvent: _handleKeyOperation, child: addContainer()); } @@ -1183,8 +1200,8 @@ Widget _getResizingCursor(DataGridConfiguration dataGridConfiguration, final int rowSpan = columnResizeController.rowSpan; final int rowIndex = columnResizeController.rowIndex; - final Color indicatorColor = themeData.columnResizeIndicatorColor; - final double strokeWidth = themeData.columnResizeIndicatorStrokeWidth; + final Color indicatorColor = themeData.columnResizeIndicatorColor!; + final double strokeWidth = themeData.columnResizeIndicatorStrokeWidth!; double rowHeight = dataGridConfiguration.container.rowHeights[rowIndex]; // Consider the spanned row height to show the indicator at center @@ -2056,24 +2073,26 @@ class VisualContainerHelper { } void _updateFreezePaneColumns(DataGridConfiguration dataGridConfiguration) { - if (dataGridConfiguration.frozenColumnsCount > 0 || - dataGridConfiguration.footerFrozenColumnsCount > 0) { - final int frozenColumnCount = grid_helper.resolveToScrollColumnIndex( - dataGridConfiguration, dataGridConfiguration.frozenColumnsCount); - if (frozenColumnCount > 0 && columnCount >= frozenColumnCount) { - frozenColumns = frozenColumnCount; - } else { - frozenColumns = 0; - } + // We need to consider the indent column along with the frozen columns count. + // So, we have added the 1 to the frozen columns count. + // We should consider the frozen columns count when the [SfDataGrid.frozenColumnsCount] is greater than 0. + final int frozenColumnCount = dataGridConfiguration.frozenColumnsCount > 0 + ? grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, dataGridConfiguration.frozenColumnsCount) + : 0; + if (frozenColumnCount > 0 && columnCount >= frozenColumnCount) { + frozenColumns = frozenColumnCount; + } else { + frozenColumns = 0; + } - final int footerFrozenColumnsCount = - dataGridConfiguration.footerFrozenColumnsCount; - if (footerFrozenColumnsCount > 0 && - columnCount > frozenColumnCount + footerFrozenColumnsCount) { - footerFrozenColumns = footerFrozenColumnsCount; - } else { - footerFrozenColumns = 0; - } + final int footerFrozenColumnsCount = + dataGridConfiguration.footerFrozenColumnsCount; + if (footerFrozenColumnsCount > 0 && + columnCount > frozenColumnCount + footerFrozenColumnsCount) { + footerFrozenColumns = footerFrozenColumnsCount; + } else { + footerFrozenColumns = 0; } } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart index 07a7e2e60..fc1ad36cb 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart @@ -895,18 +895,19 @@ class SfDataPagerState extends State { } } - Widget _getIcon(String type, IconData iconData, bool visible) { + Widget _getIcon( + String type, IconData iconData, bool visible, ThemeData flutterTheme) { Icon buildIcon() { return Icon(iconData, key: ValueKey(type), size: 20, color: visible - ? _dataPagerThemeHelper!.brightness == Brightness.light - ? _dataPagerThemeHelper!.disabledItemTextStyle.color + ? flutterTheme.brightness == Brightness.light + ? _dataPagerThemeHelper!.disabledItemTextStyle!.color ?.withOpacity(0.54) - : _dataPagerThemeHelper!.disabledItemTextStyle.color + : _dataPagerThemeHelper!.disabledItemTextStyle!.color ?.withOpacity(0.65) - : _dataPagerThemeHelper!.disabledItemTextStyle.color); + : _dataPagerThemeHelper!.disabledItemTextStyle!.color); } if (widget.direction == Axis.vertical) { @@ -1104,7 +1105,7 @@ class SfDataPagerState extends State { final ThemeData flutterTheme = Theme.of(context); Widget? pagerItem; Key? pagerItemKey; - Color itemColor = _dataPagerThemeHelper!.itemColor; + Color itemColor = _dataPagerThemeHelper!.itemColor!; bool visible = true; late Border border; @@ -1118,7 +1119,7 @@ class SfDataPagerState extends State { _dataPagerThemeHelper!.itemBorderWidth! > 0.0 ? Border.all( width: _dataPagerThemeHelper!.itemBorderWidth!, - color: _dataPagerThemeHelper!.itemBorderColor) + color: _dataPagerThemeHelper!.itemBorderColor!) : Border.all(width: 0.0, color: Colors.transparent); } @@ -1126,20 +1127,20 @@ class SfDataPagerState extends State { if (element == null) { visible = !_isNavigatorItemVisible(type!); itemColor = visible - ? _dataPagerThemeHelper!.itemColor - : _dataPagerThemeHelper!.disabledItemColor; + ? _dataPagerThemeHelper!.itemColor! + : _dataPagerThemeHelper!.disabledItemColor!; pagerItem = Semantics( label: '$type Page', - child: _getIcon(type, iconData!, visible), + child: _getIcon(type, iconData!, visible, flutterTheme), ); pagerItemKey = ObjectKey(type); } else { final bool isSelected = _checkIsSelectedIndex(element.index); itemColor = isSelected - ? _dataPagerThemeHelper!.selectedItemColor - : _dataPagerThemeHelper!.itemColor; + ? _dataPagerThemeHelper!.selectedItemColor! + : _dataPagerThemeHelper!.itemColor!; final int index = _resolveToItemIndexInView(element.index); pagerItem = Text( @@ -1168,13 +1169,13 @@ class SfDataPagerState extends State { if (element == null) { visible = !_isNavigatorItemVisible(type!); itemColor = visible - ? _dataPagerThemeHelper!.itemColor - : _dataPagerThemeHelper!.disabledItemColor; + ? _dataPagerThemeHelper!.itemColor! + : _dataPagerThemeHelper!.disabledItemColor!; } else { final bool isSelected = _checkIsSelectedIndex(element.index); itemColor = isSelected - ? _dataPagerThemeHelper!.selectedItemColor - : _dataPagerThemeHelper!.itemColor; + ? _dataPagerThemeHelper!.selectedItemColor! + : _dataPagerThemeHelper!.itemColor!; } } @@ -1309,7 +1310,7 @@ class SfDataPagerState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(3.0), border: Border.all( - color: _dataPagerThemeHelper!.dropdownButtonBorderColor)), + color: _dataPagerThemeHelper!.dropdownButtonBorderColor!)), child: DropdownButtonHideUnderline( child: DropdownButton( focusColor: Colors.transparent, @@ -1546,10 +1547,10 @@ class SfDataPagerState extends State { labelInfo, textDirection: _textDirection, style: TextStyle( - fontSize: _dataPagerThemeHelper!.itemTextStyle.fontSize, - fontWeight: _dataPagerThemeHelper!.itemTextStyle.fontWeight, - fontFamily: _dataPagerThemeHelper!.itemTextStyle.fontFamily, - color: _dataPagerThemeHelper!.itemTextStyle.color), + fontSize: _dataPagerThemeHelper!.itemTextStyle!.fontSize, + fontWeight: _dataPagerThemeHelper!.itemTextStyle!.fontWeight, + fontFamily: _dataPagerThemeHelper!.itemTextStyle!.fontFamily, + color: _dataPagerThemeHelper!.itemTextStyle!.color), ); final Widget dataPagerLabel = SizedBox( @@ -1638,8 +1639,7 @@ class SfDataPagerState extends State { textDirection = Directionality.of(context); _localization = SfLocalizations.of(context); final ThemeData themeData = Theme.of(context); - _dataPagerThemeHelper = DataPagerThemeHelper( - _dataPagerThemeData, Theme.of(context).colorScheme, themeData); + _dataPagerThemeHelper = DataPagerThemeHelper(context); _isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || @@ -1704,7 +1704,14 @@ class SfDataPagerState extends State { child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraint) { _updateConstraintChanged(constraint); - if (_currentPageIndex > _pageCount) { + // Issue: + // + // FLUT-868631-The DataPager did not function correctly when updating the rows per page at runtime + // + // Fix: We have page count value starts from 0. But the current page index starts from 1. + // So, we have to check the current page index is greater than or equal to the page count. + // If it is true, then we have to set the current page index as page count - 1. + if (_currentPageIndex >= _pageCount && _pageCount > 0) { _currentPageIndex = _pageCount - 1; } if (_isDesktop && widget.direction == Axis.horizontal) { @@ -2087,78 +2094,61 @@ class _DataPagerChangeNotifier { /// To Do class DataPagerThemeHelper { /// To Do - DataPagerThemeHelper(SfDataPagerThemeData? sfDataPagerThemeData, - ColorScheme? colorScheme, ThemeData themeData) { - brightness = sfDataPagerThemeData!.brightness ?? themeData.brightness; - backgroundColor = sfDataPagerThemeData.backgroundColor ?? - colorScheme!.surface.withOpacity(0.12); - itemColor = sfDataPagerThemeData.itemColor ?? Colors.transparent; - itemTextStyle = sfDataPagerThemeData.itemTextStyle ?? - TextStyle( - color: themeData.colorScheme.onSurface.withOpacity(0.6), - fontSize: 14, - fontFamily: 'Roboto', - fontWeight: FontWeight.w400); - selectedItemColor = - sfDataPagerThemeData.selectedItemColor ?? colorScheme!.primary; - selectedItemTextStyle = sfDataPagerThemeData.selectedItemTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onPrimary); - disabledItemColor = - sfDataPagerThemeData.disabledItemColor ?? Colors.transparent; - disabledItemTextStyle = sfDataPagerThemeData.disabledItemTextStyle ?? - TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: colorScheme!.onSurface.withOpacity(0.36)); + DataPagerThemeHelper(BuildContext context) { + final ThemeData theme = Theme.of(context); + final SfDataPagerThemeData defaults = SfDataPagerTheme.of(context)!; + final SfDataPagerThemeData effectiveDataPagerThemeData = theme.useMaterial3 + ? _SfDataPagerThemeDataM3(context) + : _SfDataPagerThemeDataM2(context); + backgroundColor = + defaults.backgroundColor ?? effectiveDataPagerThemeData.backgroundColor; + itemColor = defaults.itemColor ?? effectiveDataPagerThemeData.itemColor; + itemTextStyle = + defaults.itemTextStyle ?? effectiveDataPagerThemeData.itemTextStyle; + selectedItemColor = defaults.selectedItemColor ?? + effectiveDataPagerThemeData.selectedItemColor; + selectedItemTextStyle = defaults.selectedItemTextStyle ?? + effectiveDataPagerThemeData.selectedItemTextStyle; + disabledItemColor = defaults.disabledItemColor ?? + effectiveDataPagerThemeData.disabledItemColor; + disabledItemTextStyle = defaults.disabledItemTextStyle ?? + effectiveDataPagerThemeData.disabledItemTextStyle; itemBorderColor = - sfDataPagerThemeData.itemBorderColor ?? Colors.transparent; - itemBorderWidth = sfDataPagerThemeData.itemBorderWidth; - itemBorderRadius = - sfDataPagerThemeData.itemBorderRadius ?? BorderRadius.circular(50); - dropdownButtonBorderColor = - sfDataPagerThemeData.dropdownButtonBorderColor ?? - colorScheme!.onSurface.withOpacity(0.12); + defaults.itemBorderColor ?? effectiveDataPagerThemeData.itemBorderColor; + itemBorderWidth = + defaults.itemBorderWidth ?? effectiveDataPagerThemeData.itemBorderWidth; + itemBorderRadius = defaults.itemBorderRadius ?? + effectiveDataPagerThemeData.itemBorderRadius; + dropdownButtonBorderColor = defaults.dropdownButtonBorderColor ?? + effectiveDataPagerThemeData.dropdownButtonBorderColor; } - /// The brightness of the overall theme of the - /// application for the [SfDataPager] widgets. - /// - /// If [brightness] is not specified, then based on the - /// [Theme.of(context).brightness], brightness for - /// datapager widgets will be applied. - late Brightness brightness; - /// The color of the page Items - late Color itemColor; + late final Color? itemColor; /// The color of the data pager background - late Color backgroundColor; + late final Color? backgroundColor; /// The style of the text of page Items - late TextStyle itemTextStyle; + late final TextStyle? itemTextStyle; /// The color of the page Items which are disabled. - late Color disabledItemColor; + late final Color? disabledItemColor; /// The style of the text of page items which are disabled. - late TextStyle disabledItemTextStyle; + late final TextStyle? disabledItemTextStyle; /// The color of the currently selected page item. - late Color selectedItemColor; + late final Color? selectedItemColor; /// The style of the text of currently selected page Item. - late TextStyle selectedItemTextStyle; + late final TextStyle? selectedItemTextStyle; /// The color of the border in page Item. - late Color itemBorderColor; + late final Color? itemBorderColor; /// The width of the border in page item. - double? itemBorderWidth; + late final double? itemBorderWidth; /// If non null, the corners of the page item are rounded by /// this [itemBorderRadius]. @@ -2167,8 +2157,127 @@ class DataPagerThemeHelper { /// see also: /// /// [BoxDecoration.borderRadius] - late BorderRadiusGeometry itemBorderRadius; + late final BorderRadiusGeometry? itemBorderRadius; ///The border color of the rowsPerPage dropdown button. - late Color dropdownButtonBorderColor; + late final Color? dropdownButtonBorderColor; +} + +/// +///Defines the theme data for the [SfDataPager] widget for Material 2 design. +/// +class _SfDataPagerThemeDataM2 extends SfDataPagerThemeData { + /// Constructs the [_SfDataPagerThemeDataM2] + _SfDataPagerThemeDataM2(this.context); + + /// The build context. + final BuildContext context; + + /// The color scheme derived from the current theme context. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color get itemColor => Colors.transparent; + + @override + Color get backgroundColor => colorScheme.surface.withOpacity(0.12); + + @override + TextStyle get itemTextStyle => TextStyle( + color: colorScheme.onSurface.withOpacity(0.6), + fontSize: 14, + fontFamily: 'Roboto', + fontWeight: FontWeight.w400); + + @override + Color get disabledItemColor => Colors.transparent; + + @override + TextStyle get disabledItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface.withOpacity(0.36)); + + @override + Color get selectedItemColor => colorScheme.primary; + + @override + TextStyle get selectedItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onPrimary); + + @override + Color get itemBorderColor => Colors.transparent; + + @override + double? get itemBorderWidth => null; + + @override + BorderRadiusGeometry get itemBorderRadius => BorderRadius.circular(50); + + @override + Color get dropdownButtonBorderColor => + colorScheme.onSurface.withOpacity(0.12); +} + +/// +///Defines the theme data for the [SfDataPager] widget for Material 3 design. +/// +class _SfDataPagerThemeDataM3 extends SfDataPagerThemeData { + /// Constructs the [_SfDataPagerThemeDataM3] + _SfDataPagerThemeDataM3(this.context); + + /// The build context. + final BuildContext context; + + /// The color scheme derived from the current theme context. + late final ColorScheme colorScheme = Theme.of(context).colorScheme; + + @override + Color get itemColor => Colors.transparent; + + @override + Color get backgroundColor => colorScheme.surface.withOpacity(0.12); + + @override + TextStyle get itemTextStyle => TextStyle( + color: colorScheme.onSurface.withOpacity(0.6), + fontSize: 14, + fontFamily: 'Roboto', + fontWeight: FontWeight.w400); + + @override + Color get disabledItemColor => Colors.transparent; + + @override + TextStyle get disabledItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onSurface.withOpacity(0.36)); + + @override + Color get selectedItemColor => colorScheme.primaryContainer; + + @override + TextStyle get selectedItemTextStyle => TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: colorScheme.onPrimary); + + @override + Color get itemBorderColor => Colors.transparent; + + @override + double? get itemBorderWidth => null; + + @override + BorderRadiusGeometry get itemBorderRadius => BorderRadius.circular(50); + + @override + Color get dropdownButtonBorderColor => colorScheme.outline; } diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart index 2ce79f570..c2d0bfa61 100644 --- a/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart @@ -22,7 +22,7 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Syncfusion DataGrid Demo', - theme: ThemeData(useMaterial3: false), + theme: ThemeData(primarySwatch: Colors.blue), home: const MyHomePage(), ); } diff --git a/packages/syncfusion_flutter_datagrid_export/pubspec.yaml b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml index 98548f03a..a40aed1d7 100644 --- a/packages/syncfusion_flutter_datagrid_export/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_datagrid_export description: The Syncfusion Flutter DataGrid Export library is used to export the DataGrid content to Excel and Pdf format with several customization options. -version: 23.2.6 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datagrid_export environment: @@ -13,7 +13,7 @@ dependencies: syncfusion_flutter_datagrid: path: ../syncfusion_flutter_datagrid - + syncfusion_flutter_xlsio: path: ../syncfusion_flutter_xlsio diff --git a/packages/syncfusion_flutter_datepicker/CHANGELOG.md b/packages/syncfusion_flutter_datepicker/CHANGELOG.md index f6ba2fa6b..bedf82b0b 100644 --- a/packages/syncfusion_flutter_datepicker/CHANGELOG.md +++ b/packages/syncfusion_flutter_datepicker/CHANGELOG.md @@ -1,3 +1,16 @@ +## Unreleased + +**General** +* Provided th​e Material 3 themes support. + +**Bug fixes** +* \#FB50679 - Now, text size remains consistent when the app state or themes gets changed. + +## [24.1.46] - 17/01/2024 + +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + ## [19.4.38] - 12/17/2021 **Features** * Provided support to extendable range selection direction in the date range picker. diff --git a/packages/syncfusion_flutter_datepicker/example/lib/main.dart b/packages/syncfusion_flutter_datepicker/example/lib/main.dart index ba315d902..defe49621 100644 --- a/packages/syncfusion_flutter_datepicker/example/lib/main.dart +++ b/packages/syncfusion_flutter_datepicker/example/lib/main.dart @@ -53,7 +53,6 @@ class MyAppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar( title: const Text('DatePicker demo'), diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart index 77a1a261c..7353e38f2 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker.dart @@ -13,6 +13,7 @@ import '../../datepicker.dart'; import 'month_view.dart'; import 'picker_helper.dart'; +import 'theme.dart'; import 'year_view.dart'; /// Signature for callback that reports that the picker state value changed. @@ -5627,7 +5628,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> @override void didChangeDependencies() { - _textScaleFactor = MediaQuery.textScalerOf(context).scale(_textScaleFactor); + _textScaleFactor = MediaQuery.textScalerOf(context).scale(1); final TextDirection direction = Directionality.of(context); // default width value will be device width when the widget placed inside a // infinity width widget @@ -5887,15 +5888,19 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> SfDateRangePickerThemeData _getPickerThemeData( SfDateRangePickerThemeData pickerTheme, ThemeData themeData) { final ColorScheme colorScheme = themeData.colorScheme; + final SfDateRangePickerThemeData effectiveThemeData = themeData.useMaterial3 + ? SfDateRangePickerThemeDataM3(context) + : SfDateRangePickerThemeDataM2(context); return pickerTheme.copyWith( - brightness: pickerTheme.brightness ?? colorScheme.brightness, - backgroundColor: pickerTheme.backgroundColor ?? Colors.transparent, - headerBackgroundColor: - pickerTheme.headerBackgroundColor ?? Colors.transparent, - viewHeaderBackgroundColor: - pickerTheme.viewHeaderBackgroundColor ?? Colors.transparent, + brightness: themeData.brightness, + backgroundColor: + pickerTheme.backgroundColor ?? effectiveThemeData.backgroundColor, + headerBackgroundColor: pickerTheme.headerBackgroundColor ?? + effectiveThemeData.headerBackgroundColor, + viewHeaderBackgroundColor: pickerTheme.viewHeaderBackgroundColor ?? + effectiveThemeData.viewHeaderBackgroundColor, weekNumberBackgroundColor: pickerTheme.weekNumberBackgroundColor ?? - colorScheme.onSurface.withOpacity(0.08), + effectiveThemeData.weekNumberBackgroundColor, viewHeaderTextStyle: themeData.textTheme.bodyMedium! .copyWith( color: colorScheme.onSurface.withOpacity(0.87), @@ -6042,15 +6047,16 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> ) .merge(pickerTheme.weekendDatesTextStyle) .merge(widget.monthCellStyle.weekendTextStyle), - selectionColor: pickerTheme.selectionColor ?? colorScheme.primary, - startRangeSelectionColor: - pickerTheme.startRangeSelectionColor ?? colorScheme.primary, + selectionColor: + pickerTheme.selectionColor ?? effectiveThemeData.selectionColor, + startRangeSelectionColor: pickerTheme.startRangeSelectionColor ?? + effectiveThemeData.startRangeSelectionColor, rangeSelectionColor: pickerTheme.rangeSelectionColor ?? - colorScheme.primary.withOpacity(0.1), - endRangeSelectionColor: - pickerTheme.endRangeSelectionColor ?? colorScheme.primary, - todayHighlightColor: - pickerTheme.todayHighlightColor ?? colorScheme.primary); + effectiveThemeData.rangeSelectionColor, + endRangeSelectionColor: pickerTheme.endRangeSelectionColor ?? + effectiveThemeData.endRangeSelectionColor, + todayHighlightColor: pickerTheme.todayHighlightColor ?? + effectiveThemeData.todayHighlightColor); } void _updateFadeAnimation() { @@ -6834,13 +6840,8 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> headerWidth = pickerWidth; } - Color? backgroundColor = widget.headerStyle.backgroundColor ?? + final Color? backgroundColor = widget.headerStyle.backgroundColor ?? _datePickerTheme.headerBackgroundColor; - if (!isHorizontal && backgroundColor == Colors.transparent) { - backgroundColor = _datePickerTheme.brightness == Brightness.dark - ? Colors.grey[850]! - : Colors.white; - } final Widget header = Positioned( top: 0, left: 0, @@ -7408,9 +7409,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedDates = _getSelectedDates(_selectedDates); - if (!isSameSelectedDate) + if (!isSameSelectedDate) { _raiseSelectionChangedCallback(widget, value: _controller.selectedDates); + } } break; case DateRangePickerSelectionMode.range: @@ -7427,9 +7429,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedRange = _selectedRange; - if (!isSameSelectedDate) + if (!isSameSelectedDate) { _raiseSelectionChangedCallback(widget, value: _controller.selectedRange); + } } break; case DateRangePickerSelectionMode.multiRange: @@ -7446,9 +7449,10 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> } _controller.selectedRanges = _getSelectedRanges(_selectedRanges); - if (!isSameSelectedDate) + if (!isSameSelectedDate) { _raiseSelectionChangedCallback(widget, value: _controller.selectedRanges); + } } } } @@ -9012,7 +9016,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> : null, child: FocusScope( node: _focusNode, - onKey: _onKeyDown, + onKeyEvent: _onKeyDown, child: CustomScrollViewerLayout( _addViews(context), widget.picker.navigationDirection == @@ -10054,11 +10058,11 @@ class _PickerScrollViewState extends State<_PickerScrollView> return selectedDate; } - KeyEventResult _switchViewsByKeyBoardEvent(RawKeyEvent event) { + KeyEventResult _switchViewsByKeyBoardEvent(KeyEvent event) { /// Ctrl + and Ctrl - used by browser to zoom the page, hence as referred /// EJ2 scheduler, we have used alt + numeric to switch between views in /// datepicker web - if (event.isAltPressed) { + if (HardwareKeyboard.instance.isAltPressed) { if (event.logicalKey == LogicalKeyboardKey.digit1) { _pickerStateDetails.view = DateRangePickerView.month; } else if (event.logicalKey == LogicalKeyboardKey.digit2) { @@ -10082,7 +10086,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> _PickerViewState currentVisibleViewState, _PickerView currentVisibleView, DateRangePickerView pickerView, - RawKeyEvent event) { + KeyEvent event) { dynamic selectedDate; if (_pickerStateDetails.selectedDate != null && widget.picker.selectionMode == DateRangePickerSelectionMode.single) { @@ -10102,7 +10106,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> DateRangePickerSelectionMode.multiple && _pickerStateDetails.selectedDates != null && _pickerStateDetails.selectedDates!.isNotEmpty && - event.isShiftPressed) { + HardwareKeyboard.instance.isShiftPressed) { final dynamic date = _pickerStateDetails .selectedDates![_pickerStateDetails.selectedDates!.length - 1]; selectedDate = _getYearSelectedDate( @@ -10125,7 +10129,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> DateRangePickerSelectionMode.extendableRange) && _pickerStateDetails.selectedRange != null && _pickerStateDetails.selectedRange.startDate != null && - event.isShiftPressed) { + HardwareKeyboard.instance.isShiftPressed) { final dynamic date = currentVisibleViewState._lastSelectedDate; selectedDate = _getYearSelectedDate( date, event.logicalKey, currentVisibleView, currentVisibleViewState); @@ -10308,13 +10312,14 @@ class _PickerScrollViewState extends State<_PickerScrollView> } } - KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { + KeyEventResult _onKeyDown(FocusNode node, KeyEvent event) { KeyEventResult result = KeyEventResult.ignored; - if (event.runtimeType != RawKeyDownEvent) { + if (event.runtimeType != KeyDownEvent) { return result; } - if (event.isShiftPressed && event.logicalKey == LogicalKeyboardKey.tab) { + if (HardwareKeyboard.instance.isShiftPressed && + event.logicalKey == LogicalKeyboardKey.tab) { FocusScope.of(context).previousFocus(); return KeyEventResult.handled; } @@ -10328,7 +10333,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> result = _switchViewsByKeyBoardEvent(event); - if (event.isControlPressed) { + if (HardwareKeyboard.instance.isControlPressed) { final bool canMoveToNextView = DateRangePickerHelper.canMoveToNextViewRtl( pickerView, DateRangePickerHelper.getNumberOfWeeksInView( @@ -10505,7 +10510,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> } dynamic _updateSingleSelectionByKeyBoardKeys( - RawKeyEvent event, _PickerView currentView) { + KeyEvent event, _PickerView currentView) { dynamic selectedDate = _pickerStateDetails.selectedDate; if (event.logicalKey == LogicalKeyboardKey.arrowRight) { if (isSameDate(_pickerStateDetails.selectedDate, @@ -10553,10 +10558,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> return null; } - dynamic _updateMultiAndRangeSelectionByKeyBoard(RawKeyEvent event, - _PickerViewState currentState, _PickerView currentView) { + dynamic _updateMultiAndRangeSelectionByKeyBoard( + KeyEvent event, _PickerViewState currentState, _PickerView currentView) { dynamic selectedDate; - if (event.isShiftPressed && + if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowRight) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { @@ -10579,7 +10584,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> selectedDate)); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowLeft) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { @@ -10602,7 +10607,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> selectedDate)); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowUp) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { @@ -10625,7 +10630,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> selectedDate)); return selectedDate; } - } else if (event.isShiftPressed && + } else if (HardwareKeyboard.instance.isShiftPressed && event.logicalKey == LogicalKeyboardKey.arrowDown) { if (widget.picker.selectionMode == DateRangePickerSelectionMode.multiple) { @@ -10652,8 +10657,8 @@ class _PickerScrollViewState extends State<_PickerScrollView> return null; } - dynamic _updateSelectedDate(RawKeyEvent event, _PickerViewState currentState, - _PickerView currentView) { + dynamic _updateSelectedDate( + KeyEvent event, _PickerViewState currentState, _PickerView currentView) { switch (widget.picker.selectionMode) { case DateRangePickerSelectionMode.single: { diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart index 669a04f5c..435144396 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/date_picker_manager.dart @@ -164,7 +164,7 @@ class DateRangePickerHeaderStyle with Diagnosticable { final Color? backgroundColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -321,7 +321,7 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -448,7 +448,7 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { final TextStyle? textStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1070,7 +1070,7 @@ class DateRangePickerMonthViewSettings with Diagnosticable { final DateRangePickerWeekNumberStyle weekNumberStyle; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1631,7 +1631,7 @@ class DateRangePickerYearCellStyle with Diagnosticable { final Decoration? leadingDatesDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -3055,7 +3055,7 @@ class DateRangePickerMonthCellStyle with Diagnosticable { final Color? endRangeSelectionColor; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart index 189452562..d347ecf81 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/hijri_date_picker_manager.dart @@ -463,7 +463,7 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { final List weekendDays; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -895,7 +895,7 @@ class HijriDatePickerYearCellStyle with Diagnosticable { final Decoration? todayCellDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } @@ -1732,7 +1732,7 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { final Decoration? todayCellDecoration; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (identical(this, other)) { return true; } diff --git a/packages/syncfusion_flutter_datepicker/pubspec.yaml b/packages/syncfusion_flutter_datepicker/pubspec.yaml index 3fbfd87f0..45577dd44 100644 --- a/packages/syncfusion_flutter_datepicker/pubspec.yaml +++ b/packages/syncfusion_flutter_datepicker/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_datepicker description: The Flutter Date Range Picker widget allows users to easily select dates or a range of dates. It has four built-in views that allow quick navigation to the desired date. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datepicker screenshots: @@ -31,7 +31,7 @@ environment: dependencies: flutter: sdk: flutter - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_gauges/CHANGELOG.md b/packages/syncfusion_flutter_gauges/CHANGELOG.md index 964db90a1..86b3f52fc 100644 --- a/packages/syncfusion_flutter_gauges/CHANGELOG.md +++ b/packages/syncfusion_flutter_gauges/CHANGELOG.md @@ -1,4 +1,14 @@ -## [23.1.43] - 10/31/2023 +## Unreleased + +**General** +* Provided th​e Material 3 themes support. + +## [24.1.46] - 17/01/2024 + +**General** +* Upgraded the `intl` package to the latest version 0.19.0. + +# [23.1.43] - 10/31/2023 ## Radial Gauge diff --git a/packages/syncfusion_flutter_gauges/example/lib/main.dart b/packages/syncfusion_flutter_gauges/example/lib/main.dart index b2a79a588..08ea5b7e0 100644 --- a/packages/syncfusion_flutter_gauges/example/lib/main.dart +++ b/packages/syncfusion_flutter_gauges/example/lib/main.dart @@ -11,10 +11,7 @@ class GaugeApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( title: 'Radial Gauge Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - useMaterial3: false, - ), + theme: ThemeData(primarySwatch: Colors.blue), home: MyHomePage(), ); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart index a20e4f8e2..c6c009d2f 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_renderer.dart @@ -35,21 +35,36 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { final Color? majorTickColor = linearGauge.majorTickStyle.color; final Color? minorTickColor = linearGauge.minorTickStyle.color; final ThemeData theme = Theme.of(context); + final bool isMaterial3 = theme.useMaterial3; final SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; final bool isDarkTheme = theme.brightness == Brightness.dark; + final Color axisLineColor = isMaterial3 + ? theme.colorScheme.surfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.24) + : theme.colorScheme.onSurface.withOpacity(0.12); + final Color axisBorderColor = isMaterial3 + ? theme.colorScheme.surfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); + final Color majorTick = isMaterial3 + ? theme.colorScheme.outlineVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); + final Color minorTick = isMaterial3 + ? theme.colorScheme.outlineVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); return RenderLinearAxis( orientation: linearGauge.orientation, showAxisTrack: linearGauge.showAxisTrack, thickness: style.thickness, - color: style.color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.24) - : theme.colorScheme.onSurface.withOpacity(0.12)), + color: style.color ?? axisLineColor, borderWidth: style.borderWidth, - borderColor: style.borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + borderColor: style.borderColor ?? axisBorderColor, gradient: style.gradient, edgeStyle: style.edgeStyle, minimum: linearGauge.minimum, @@ -65,9 +80,11 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { textStyle: theme.textTheme.bodySmall! .copyWith( color: gaugeThemeData.axisLabelColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface - : theme.colorScheme.onSurface.withOpacity(0.87))) + (isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface + : theme.colorScheme.onSurface.withOpacity(0.87))) .merge(gaugeThemeData.axisLabelTextStyle) .merge(linearGauge.axisLabelStyle), showLabels: linearGauge.showLabels, @@ -76,16 +93,10 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { useRangeColorForAxis: linearGauge.useRangeColorForAxis, majorTickLength: linearGauge.majorTickStyle.length, majorTickThickness: linearGauge.majorTickStyle.thickness, - majorTickColor: majorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + majorTickColor: majorTickColor ?? majorTick, minorTickLength: linearGauge.minorTickStyle.length, minorTickThickness: linearGauge.minorTickStyle.thickness, - minorTickColor: minorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)), + minorTickColor: minorTickColor ?? minorTick, maximumLabels: linearGauge.maximumLabels, axisTrackExtent: linearGauge.axisTrackExtent, fadeAnimation: fadeAnimation, @@ -104,20 +115,40 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { final Color? majorTickColor = linearGauge.majorTickStyle.color; final Color? minorTickColor = linearGauge.minorTickStyle.color; final ThemeData theme = Theme.of(context); + final bool isMaterial3 = theme.useMaterial3; final SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; final bool isDarkTheme = theme.brightness == Brightness.dark; + final Color axisLabelColor = isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : (isDarkTheme + ? theme.colorScheme.onSurface + : theme.colorScheme.onSurface.withOpacity(0.87)); + final Color axisLineColor = isMaterial3 + ? theme.colorScheme.surfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.24) + : theme.colorScheme.onSurface.withOpacity(0.12); + final Color axisBorderColor = isMaterial3 + ? theme.colorScheme.surfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); + final Color majorTick = isMaterial3 + ? theme.colorScheme.outlineVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); + final Color minorTick = isMaterial3 + ? theme.colorScheme.outlineVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.30) + : theme.colorScheme.onSurface.withOpacity(0.26); renderObject ..orientation = linearGauge.orientation ..showAxisTrack = linearGauge.showAxisTrack ..thickness = style.thickness - ..color = style.color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.24) - : theme.colorScheme.onSurface.withOpacity(0.12)) - ..borderColor = style.borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..color = style.color ?? axisLineColor + ..borderColor = style.borderColor ?? axisBorderColor ..borderWidth = style.borderWidth ..gradient = style.gradient ..edgeStyle = style.edgeStyle @@ -131,24 +162,14 @@ class LinearAxisRenderObjectWidget extends LeafRenderObjectWidget { ..tickPosition = linearGauge.tickPosition ..majorTickLength = linearGauge.majorTickStyle.length ..majorTickThickness = linearGauge.majorTickStyle.thickness - ..majorTickColor = majorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..majorTickColor = majorTickColor ?? majorTick ..minorTickLength = linearGauge.minorTickStyle.length ..minorTickThickness = linearGauge.minorTickStyle.thickness - ..minorTickColor = minorTickColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.30) - : theme.colorScheme.onSurface.withOpacity(0.26)) + ..minorTickColor = minorTickColor ?? minorTick ..tickOffset = linearGauge.tickOffset ..isMirrored = linearGauge.isMirrored ..textStyle = theme.textTheme.bodySmall! - .copyWith( - color: gaugeThemeData.axisLabelColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface - : theme.colorScheme.onSurface.withOpacity(0.87))) + .copyWith(color: gaugeThemeData.axisLabelColor ?? axisLabelColor) .merge(gaugeThemeData.axisLabelTextStyle) .merge(linearGauge.axisLabelStyle) ..showLabels = linearGauge.showLabels diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart index 601a0694b..aac73127a 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_pointer.dart @@ -270,12 +270,17 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { RenderObject createRenderObject(BuildContext context) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; + final bool isDarkTheme = themeData.brightness == Brightness.dark; + final Color barPointerColor = isMaterial3 + ? (isDarkTheme ? const Color(0XFFFFF500) : const Color(0XFF06AEE0)) + : themeData.colorScheme.primary; return RenderLinearBarPointer( value: value, edgeStyle: edgeStyle, shaderCallback: shaderCallback, - color: color ?? themeData.colorScheme.primary, - borderColor: borderColor ?? themeData.colorScheme.primary, + color: color ?? barPointerColor, + borderColor: borderColor ?? barPointerColor, borderWidth: borderWidth, thickness: thickness, offset: offset, @@ -292,12 +297,17 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { BuildContext context, RenderLinearBarPointer renderObject) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; + final bool isDarkTheme = themeData.brightness == Brightness.dark; + final Color barPointerColor = isMaterial3 + ? (isDarkTheme ? const Color(0XFFFFF500) : const Color(0XFF06AEE0)) + : themeData.colorScheme.primary; renderObject ..value = value ..edgeStyle = edgeStyle ..shaderCallback = shaderCallback - ..color = color ?? themeData.colorScheme.primary - ..borderColor = borderColor ?? themeData.colorScheme.primary + ..color = color ?? barPointerColor + ..borderColor = borderColor ?? barPointerColor ..borderWidth = borderWidth ..thickness = thickness ..offset = offset diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart index daf132e45..068042230 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_pointer.dart @@ -442,19 +442,19 @@ class LinearShapePointer extends LeafRenderObjectWidget final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; + final bool isMaterial3 = theme.useMaterial3; + final Color shapePointerColor = isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.70) + : theme.colorScheme.onSurface.withOpacity(0.54); return RenderLinearShapePointer( value: value, onChanged: onChanged, onChangeStart: onChangeStart, onChangeEnd: onChangeEnd, - color: color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)), - borderColor: borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)), + color: color ?? shapePointerColor, + borderColor: borderColor ?? shapePointerColor, borderWidth: borderWidth, width: width ?? (shapeType == LinearShapePointerType.diamond ? 12 : 16), height: @@ -480,20 +480,20 @@ class LinearShapePointer extends LeafRenderObjectWidget final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); final ThemeData theme = Theme.of(context); final bool isDarkTheme = theme.brightness == Brightness.dark; + final bool isMaterial3 = theme.useMaterial3; + final Color shapePointerColor = isMaterial3 + ? theme.colorScheme.onSurfaceVariant + : isDarkTheme + ? theme.colorScheme.onSurface.withOpacity(0.70) + : theme.colorScheme.onSurface.withOpacity(0.54); renderObject ..value = value ..onChanged = onChanged ..onChangeStart = onChangeStart ..onChangeEnd = onChangeEnd - ..color = color ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)) - ..borderColor = borderColor ?? - (isDarkTheme - ? theme.colorScheme.onSurface.withOpacity(0.70) - : theme.colorScheme.onSurface.withOpacity(0.54)) + ..color = color ?? shapePointerColor + ..borderColor = borderColor ?? shapePointerColor ..borderWidth = borderWidth ..width = width ?? (shapeType == LinearShapePointerType.diamond ? 12 : 16) ..height = diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart index 311c05b9a..f0b8240e2 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_widget.dart @@ -32,6 +32,7 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { final MajorTickStyle majorTickStyle = axis.majorTickStyle; final MinorTickStyle minorTickStyle = axis.minorTickStyle; final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); RadialAxisRenderer? renderer; if (axis.onCreateAxisRenderer != null) { @@ -89,7 +90,7 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { axisElementsAnimation: radialGaugeScope.animation1, repaintNotifier: radialGaugeScope.repaintNotifier, gaugeThemeData: gaugeTheme, - context: context, + themeData: themeData, ranges: axis.ranges, renderer: renderer, backgroundImage: axis.backgroundImage, @@ -105,6 +106,7 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { final MajorTickStyle majorTickStyle = axis.majorTickStyle; final MinorTickStyle minorTickStyle = axis.minorTickStyle; final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); RadialAxisRenderer? renderer; if (axis.onCreateAxisRenderer != null) { @@ -162,6 +164,7 @@ class RadialAxisRenderObjectWidget extends LeafRenderObjectWidget { ..axisLineAnimation = radialGaugeScope.animation ..axisElementsAnimation = radialGaugeScope.animation1 ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..renderer = renderer ..imageStream = axis.backgroundImage?.resolve(createLocalImageConfiguration(context)) @@ -220,7 +223,7 @@ class RenderRadialAxisWidget extends RenderBox { Color? minorTickColor, List? minorTickDashArray, required SfGaugeThemeData gaugeThemeData, - required BuildContext context, + required ThemeData themeData, RadialAxisRenderer? renderer, List? ranges, Animation? axisElementsAnimation, @@ -280,8 +283,7 @@ class RenderRadialAxisWidget extends RenderBox { _ranges = ranges, _renderer = renderer, _repaintNotifier = repaintNotifier, - _themeData = Theme.of(context), - _isDarkTheme = Theme.of(context).brightness == Brightness.dark, + _themeData = themeData, _backgroundImage = backgroundImage { _isLabelsOutside = labelPosition == ElementsPosition.outside; _isTicksOutside = tickPosition == ElementsPosition.outside; @@ -313,8 +315,6 @@ class RenderRadialAxisWidget extends RenderBox { late double _cornerAngle; ImageInfo? _backgroundImageInfo; late ImageStreamListener _imageStreamListener; - final ThemeData _themeData; - final bool _isDarkTheme; late double _radius; late double _actualAxisWidth; @@ -404,6 +404,19 @@ class RenderRadialAxisWidget extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + /// Gets the startAngle assigned to [RenderRadialAxisWidget]. double get startAngle => _startAngle; double _startAngle; @@ -2374,12 +2387,15 @@ class RenderRadialAxisWidget extends RenderBox { } Paint _getPaint(SweepGradient? gradient, bool isFill) { + final bool isDarkTheme = _themeData.brightness == Brightness.dark; final Paint paint = Paint() ..color = axisLineColor ?? _gaugeThemeData.axisLineColor ?? - (_isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.24) - : _themeData.colorScheme.onSurface.withOpacity(0.12)) + (_themeData.useMaterial3 + ? _themeData.colorScheme.surfaceVariant + : (isDarkTheme + ? _themeData.colorScheme.onSurface.withOpacity(0.24) + : _themeData.colorScheme.onSurface.withOpacity(0.12))) ..style = !isFill ? PaintingStyle.stroke : PaintingStyle.fill ..strokeWidth = _actualAxisWidth; if (gradient != null) { @@ -2459,11 +2475,13 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw the major ticks - void _drawMajorTicks(Canvas canvas) { + void _drawMajorTicks(Canvas canvas, bool isDarkTheme) { double length = _majorTickOffsets.length.toDouble(); - final Color colorSchemeMajorTickColor = _isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.27) - : _themeData.colorScheme.onSurface.withOpacity(0.18); + final Color colorSchemeMajorTickColor = _themeData.useMaterial3 + ? _themeData.colorScheme.outlineVariant + : (isDarkTheme + ? _themeData.colorScheme.onSurface.withOpacity(0.27) + : _themeData.colorScheme.onSurface.withOpacity(0.18)); if (_axisElementsAnimation != null) { length = _majorTickOffsets.length * _axisElementsAnimation!.value; } @@ -2520,11 +2538,13 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw the minor ticks. - void _drawMinorTicks(Canvas canvas) { + void _drawMinorTicks(Canvas canvas, bool isDarkTheme) { double length = _minorTickOffsets.length.toDouble(); - final Color colorSchemeMinorTickColor = _isDarkTheme - ? _themeData.colorScheme.onSurface.withOpacity(0.33) - : _themeData.colorScheme.onSurface.withOpacity(0.28); + final Color colorSchemeMinorTickColor = _themeData.useMaterial3 + ? _themeData.colorScheme.outlineVariant + : (isDarkTheme + ? _themeData.colorScheme.onSurface.withOpacity(0.33) + : _themeData.colorScheme.onSurface.withOpacity(0.28)); if (_axisElementsAnimation != null) { length = _minorTickOffsets.length * _axisElementsAnimation!.value; } @@ -2559,7 +2579,7 @@ class RenderRadialAxisWidget extends RenderBox { } /// Method to draw the axis labels. - void _drawAxisLabels(Canvas canvas) { + void _drawAxisLabels(Canvas canvas, bool isDarkTheme) { double length = _axisLabels!.length.toDouble(); if (_axisElementsAnimation != null) { length = _axisLabels!.length * _axisElementsAnimation!.value; @@ -2571,9 +2591,11 @@ class RenderRadialAxisWidget extends RenderBox { final Color labelColor = label.labelStyle.color ?? _gaugeThemeData.axisLabelTextStyle?.color ?? _gaugeThemeData.axisLabelColor ?? - (_isDarkTheme + (_themeData.useMaterial3 ? _themeData.colorScheme.onSurface - : _themeData.colorScheme.onSurface.withOpacity(0.72)); + : (isDarkTheme + ? _themeData.colorScheme.onSurface + : _themeData.colorScheme.onSurface.withOpacity(0.72))); final TextStyle axisLabelTextStyle = _themeData.textTheme.bodySmall!.copyWith( color: ranges != null && ranges!.isNotEmpty && useRangeColorForAxis @@ -2626,6 +2648,7 @@ class RenderRadialAxisWidget extends RenderBox { @override void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; + final bool isDarkTheme = _themeData.brightness == Brightness.dark; _calculateAxisElementsPosition(); if (backgroundImage != null && _backgroundImageInfo != null) { Rect rect; @@ -2656,12 +2679,12 @@ class RenderRadialAxisWidget extends RenderBox { } if (showTicks) { - _drawMajorTicks(canvas); - _drawMinorTicks(canvas); + _drawMajorTicks(canvas, isDarkTheme); + _drawMinorTicks(canvas, isDarkTheme); } if (showLabels) { - _drawAxisLabels(canvas); + _drawAxisLabels(canvas, isDarkTheme); } } } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart index ff98027a1..232c14714 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/gauge/radial_gauge.dart @@ -161,11 +161,17 @@ class SfRadialGaugeState extends State SfGaugeThemeData _updateThemeData(BuildContext context) { SfGaugeThemeData gaugeThemeData = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); + final bool isMaterial3 = themeData.useMaterial3; gaugeThemeData = gaugeThemeData.copyWith( titleTextStyle: Theme.of(context) .textTheme .bodySmall! - .copyWith(color: gaugeThemeData.titleColor, fontSize: 15) + .copyWith( + color: + gaugeThemeData.titleColor ?? themeData.colorScheme.onSurface, + fontSize: isMaterial3 ? 16 : 15, + ) .merge(gaugeThemeData.titleTextStyle) .merge(widget.title?.textStyle)); return gaugeThemeData; diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart index 119178dcc..95f5cedfc 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer.dart @@ -654,6 +654,7 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -694,13 +695,14 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { pointerAnimationController: radialAxisScope.animationController, repaintNotifier: radialAxisScope.repaintNotifier, gaugeThemeData: gaugeTheme, - context: context); + themeData: themeData); } @override void updateRenderObject( BuildContext context, RenderMarkerPointer renderObject) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -738,6 +740,7 @@ class MarkerPointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart index 52b7372a1..c90534924 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/marker_pointer_renderer.dart @@ -18,37 +18,37 @@ import '../../radial_gauge/utils/radial_callback_args.dart'; /// Represents the renderer of radial gauge marker pointer. class RenderMarkerPointer extends RenderBox { /// Creates a object for [RenderMarkerPointer]. - RenderMarkerPointer( - {required double value, - required this.enableDragging, - this.onValueChanged, - this.onValueChangeStart, - this.onValueChangeEnd, - this.onValueChanging, - required MarkerType markerType, - Color? color, - required double markerWidth, - required double markerHeight, - required double borderWidth, - required double markerOffset, - String? text, - Color? borderColor, - required GaugeSizeUnit offsetUnit, - String? imageUrl, - MarkerPointerRenderer? markerPointerRenderer, - required GaugeTextStyle textStyle, - required BuildContext context, - Color? overlayColor, - double? overlayRadius, - double elevation = 0, - AnimationController? pointerAnimationController, - this.pointerInterval, - required this.animationType, - required this.enableAnimation, - required this.isRadialGaugeAnimationEnabled, - required ValueNotifier repaintNotifier, - required SfGaugeThemeData gaugeThemeData}) - : _value = value, + RenderMarkerPointer({ + required double value, + required this.enableDragging, + this.onValueChanged, + this.onValueChangeStart, + this.onValueChangeEnd, + this.onValueChanging, + required MarkerType markerType, + Color? color, + required double markerWidth, + required double markerHeight, + required double borderWidth, + required double markerOffset, + String? text, + Color? borderColor, + required GaugeSizeUnit offsetUnit, + String? imageUrl, + MarkerPointerRenderer? markerPointerRenderer, + required GaugeTextStyle textStyle, + Color? overlayColor, + double? overlayRadius, + double elevation = 0, + AnimationController? pointerAnimationController, + this.pointerInterval, + required this.animationType, + required this.enableAnimation, + required this.isRadialGaugeAnimationEnabled, + required ValueNotifier repaintNotifier, + required SfGaugeThemeData gaugeThemeData, + required ThemeData themeData, + }) : _value = value, _markerType = markerType, _color = color, _markerWidth = markerWidth, @@ -67,8 +67,7 @@ class RenderMarkerPointer extends RenderBox { _pointerAnimationController = pointerAnimationController, _repaintNotifier = repaintNotifier, _gaugeThemeData = gaugeThemeData, - _themeData = Theme.of(context), - _isDarkTheme = Theme.of(context).brightness == Brightness.dark; + _themeData = themeData; final double _margin = 15; dart_ui.Image? _image; @@ -88,8 +87,6 @@ class RenderMarkerPointer extends RenderBox { late double _centerXPoint; late double _centerYPoint; late Offset _axisCenter; - final bool _isDarkTheme; - final ThemeData _themeData; /// Marker pointer old value. double? oldValue; @@ -209,6 +206,19 @@ class RenderMarkerPointer extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + /// Gets the value assigned to [RenderMarkerPointer]. double get value => _value; double _value; @@ -628,7 +638,9 @@ class RenderMarkerPointer extends RenderBox { final Paint paint = Paint() ..color = color ?? gaugeThemeData.markerColor ?? - _themeData.colorScheme.secondaryContainer.withOpacity(0.8) + (_themeData.useMaterial3 + ? _themeData.colorScheme.onSurfaceVariant + : _themeData.colorScheme.secondaryContainer.withOpacity(0.8)) ..style = PaintingStyle.fill; const Color shadowColor = Colors.black; @@ -718,13 +730,16 @@ class RenderMarkerPointer extends RenderBox { /// To render the MarkerShape.Text void _drawText(Canvas canvas, Paint paint, Offset startPosition, double pointerAngle, SfGaugeThemeData gaugeThemeData) { + final bool isDarkTheme = _themeData.brightness == Brightness.dark; final TextStyle markerTextStyle = _themeData.textTheme.bodySmall!.copyWith( color: textStyle.color ?? _gaugeThemeData.markerTextStyle?.color ?? _gaugeThemeData.axisLabelColor ?? - (_isDarkTheme + (_themeData.useMaterial3 ? _themeData.colorScheme.onSurface - : _themeData.colorScheme.onSurface.withOpacity(0.72)), + : (isDarkTheme + ? _themeData.colorScheme.onSurface + : _themeData.colorScheme.onSurface.withOpacity(0.72))), fontSize: textStyle.fontSize ?? _gaugeThemeData.markerTextStyle?.fontSize, fontFamily: textStyle.fontFamily ?? _gaugeThemeData.markerTextStyle?.fontFamily, diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart index 4436cc7de..c4dadb2c1 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer.dart @@ -505,6 +505,7 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -539,13 +540,14 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { animationType: animationType, repaintNotifier: radialAxisScope.repaintNotifier, gaugeThemeData: gaugeTheme, - context: context); + themeData: themeData); } @override void updateRenderObject( BuildContext context, RenderNeedlePointer renderObject) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -577,6 +579,7 @@ class NeedlePointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart index 1b523038c..d8fc22dbf 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/needle_pointer_renderer.dart @@ -38,7 +38,7 @@ class RenderNeedlePointer extends RenderBox { required this.enableAnimation, required this.isRadialGaugeAnimationEnabled, required ValueNotifier repaintNotifier, - required BuildContext context, + required ThemeData themeData, required SfGaugeThemeData gaugeThemeData}) : _value = value, _knobStyle = knobStyle, @@ -52,7 +52,7 @@ class RenderNeedlePointer extends RenderBox { _repaintNotifier = repaintNotifier, _pointerAnimationController = pointerAnimationController, _needlePointerRenderer = needlePointerRenderer, - _themeData = Theme.of(context), + _themeData = themeData, _gaugeThemeData = gaugeThemeData; double _actualTailLength = 0; @@ -86,7 +86,6 @@ class RenderNeedlePointer extends RenderBox { late double _radius; late double _sweepAngle; late Offset _axisCenter; - final ThemeData _themeData; /// Marker pointer old value. double? oldValue; @@ -181,6 +180,19 @@ class RenderNeedlePointer extends RenderBox { markNeedsPaint(); } + /// Gets the themeData assigned to [RenderRadialAxisWidget]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the themeData for [RenderRadialAxisWidget]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + /// Gets the animation assigned to [RenderNeedlePointer]. Animation? get pointerAnimation => _pointerAnimation; Animation? _pointerAnimation; @@ -567,7 +579,9 @@ class RenderNeedlePointer extends RenderBox { final Paint paint = Paint() ..color = needleColor ?? _gaugeThemeData.needleColor ?? - _themeData.colorScheme.onSurface + (_themeData.useMaterial3 + ? _themeData.colorScheme.onSurfaceVariant + : _themeData.colorScheme.onSurface) ..style = PaintingStyle.fill; final Path path = Path(); @@ -604,7 +618,9 @@ class RenderNeedlePointer extends RenderBox { final Paint tailPaint = Paint() ..color = tailStyle!.color ?? _gaugeThemeData.tailColor ?? - _themeData.colorScheme.onSurface; + (_themeData.useMaterial3 + ? _themeData.colorScheme.onSurfaceVariant + : _themeData.colorScheme.onSurface); if (tailStyle!.gradient != null) { tailPaint.shader = @@ -631,7 +647,9 @@ class RenderNeedlePointer extends RenderBox { final Paint knobPaint = Paint() ..color = knobStyle.color ?? gaugeThemeData.knobColor ?? - _themeData.colorScheme.onSurface; + (_themeData.useMaterial3 + ? _themeData.colorScheme.onSurfaceVariant + : _themeData.colorScheme.onSurface); canvas.drawCircle(_axisCenter, _actualCapRadius, knobPaint); diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart index 36a9d0d0c..329afd3cd 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer.dart @@ -449,6 +449,7 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { @override RenderObject createRenderObject(BuildContext context) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -474,7 +475,7 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { radialAxisScope.isRadialGaugeAnimationEnabled, repaintNotifier: radialAxisScope.repaintNotifier, animationType: animationType, - context: context, + themeData: themeData, gaugeThemeData: gaugeTheme); } @@ -482,6 +483,7 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { void updateRenderObject( BuildContext context, RenderRangePointer renderObject) { final SfGaugeThemeData gaugeTheme = SfGaugeTheme.of(context)!; + final ThemeData themeData = Theme.of(context); final RadialAxisScope radialAxisScope = RadialAxisScope.of(context); final RadialAxisInheritedWidget ancestor = context .dependOnInheritedWidgetOfExactType()!; @@ -506,6 +508,7 @@ class RangePointer extends LeafRenderObjectWidget implements GaugePointer { ..isRadialGaugeAnimationEnabled = radialAxisScope.isRadialGaugeAnimationEnabled ..gaugeThemeData = gaugeTheme + ..themeData = themeData ..value = value.clamp(ancestor.minimum, ancestor.maximum); super.updateRenderObject(context, renderObject); } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart index e283fe2c6..935828a88 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/pointers/range_pointer_renderer.dart @@ -32,7 +32,7 @@ class RenderRangePointer extends RenderBox { required this.enableAnimation, required this.isRadialGaugeAnimationEnabled, required ValueNotifier repaintNotifier, - required BuildContext context, + required ThemeData themeData, required SfGaugeThemeData gaugeThemeData}) : _value = value, _cornerStyle = cornerStyle, @@ -44,7 +44,7 @@ class RenderRangePointer extends RenderBox { _color = color, _pointerAnimationController = pointerAnimationController, _repaintNotifier = repaintNotifier, - _themeData = Theme.of(context), + _themeData = themeData, _gaugeThemeData = gaugeThemeData; late double _rangeArcTop; @@ -67,7 +67,6 @@ class RenderRangePointer extends RenderBox { late Offset _axisCenter; bool _isAnimating = true; bool _isInitialLoading = true; - final ThemeData _themeData; /// Range pointer animation start value. double? animationStartValue; @@ -167,6 +166,19 @@ class RenderRangePointer extends RenderBox { markNeedsPaint(); } + /// Gets the gaugeThemeData assigned to [RenderRangePointer]. + ThemeData get themeData => _themeData; + ThemeData _themeData; + + /// Sets the gaugeThemeData for [RenderMarkerPointer]. + set themeData(ThemeData value) { + if (value == _themeData) { + return; + } + _themeData = value; + markNeedsPaint(); + } + /// Gets the animation assigned to [RenderRangePointer]. Animation? get pointerAnimation => _pointerAnimation; Animation? _pointerAnimation; @@ -565,7 +577,10 @@ class RenderRangePointer extends RenderBox { final Paint paint = Paint() ..color = color ?? gaugeThemeData.rangePointerColor ?? - _themeData.colorScheme.secondaryContainer.withOpacity(0.8) + (_themeData.useMaterial3 + ? _themeData.colorScheme.primary + : _themeData.colorScheme.secondaryContainer) + .withOpacity(0.8) ..strokeWidth = _actualRangeThickness ..style = isFill ? PaintingStyle.fill : PaintingStyle.stroke; diff --git a/packages/syncfusion_flutter_gauges/pubspec.yaml b/packages/syncfusion_flutter_gauges/pubspec.yaml index 1c9c5c5d3..e9400b8bd 100644 --- a/packages/syncfusion_flutter_gauges/pubspec.yaml +++ b/packages/syncfusion_flutter_gauges/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_gauges description: The Flutter gauges library includes a linear gauge and radial gauge (a.k.a. circular gauge) to create modern, interactive, animated gauges and radial sliders. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_gauges environment: @@ -11,7 +11,7 @@ dependencies: sdk: flutter syncfusion_flutter_core: path: ../syncfusion_flutter_core - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' dev_dependencies: flutter_test: diff --git a/packages/syncfusion_flutter_maps/CHANGELOG.md b/packages/syncfusion_flutter_maps/CHANGELOG.md index 9f33b344d..e679ee388 100644 --- a/packages/syncfusion_flutter_maps/CHANGELOG.md +++ b/packages/syncfusion_flutter_maps/CHANGELOG.md @@ -1,5 +1,11 @@ ## Unreleased +**General** + +* Provided th​e Material 3 themes support. + +## [22.2.8] - 08/08/2023 + **Bugs** * #FB45437 - Resolved the late error exception when assigning any value other than -1 to the selectedIndex property. diff --git a/packages/syncfusion_flutter_maps/example/lib/main.dart b/packages/syncfusion_flutter_maps/example/lib/main.dart index b90a0b729..9c40b5912 100644 --- a/packages/syncfusion_flutter_maps/example/lib/main.dart +++ b/packages/syncfusion_flutter_maps/example/lib/main.dart @@ -9,12 +9,9 @@ void main() { class MapsApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Maps Demo', - theme: ThemeData( - useMaterial3: false, - ), - home: const MyHomePage(), + home: MyHomePage(), ); } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart index cd5acfb31..29356c8fe 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart @@ -2426,7 +2426,7 @@ class _LegendState extends State { position: _getEffectivePosition(widget.legend.position), overflowMode: _getEffectiveOverflowMode(widget.legend.overflowMode), itemSpacing: widget.legend.spacing, - textStyle: widget.legend.textStyle, + textStyle: widget.themeData.legendTextStyle, iconType: _getEffectiveLegendIconType(widget.legend.iconType), iconSize: widget.legend.iconSize, toggledIndices: _toggledIndices, @@ -2455,7 +2455,7 @@ class _LegendState extends State { direction: widget.legend.direction, offset: widget.legend.offset, padding: widget.legend.padding, - textStyle: widget.legend.textStyle, + textStyle: widget.themeData.legendTextStyle, labelsPlacement: _getEffectiveLabelPlacement(_getActualLabelsPlacement()), edgeLabelsPlacement: _getEffectiveEdgeLabelsPlacement( diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart index 2a4032071..7e4d429e3 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart @@ -15,6 +15,7 @@ class MarkerContainer extends Stack { MarkerContainer({ required this.markerTooltipBuilder, required this.controller, + required this.themeData, this.sublayer, this.ancestor, List? children, @@ -22,6 +23,7 @@ class MarkerContainer extends Stack { final IndexedWidgetBuilder? markerTooltipBuilder; final MapController controller; + final SfMapsThemeData themeData; final MapShapeSublayer? sublayer; final MapLayerInheritedWidget? ancestor; @@ -30,6 +32,7 @@ class MarkerContainer extends Stack { return _RenderMarkerContainer() ..markerTooltipBuilder = markerTooltipBuilder ..controller = controller + ..themeData = themeData ..sublayer = sublayer ..container = this; } @@ -41,6 +44,7 @@ class MarkerContainer extends Stack { _RenderMarkerContainer renderObject) { renderObject ..markerTooltipBuilder = markerTooltipBuilder + ..themeData = themeData ..sublayer = sublayer ..container = this; } @@ -49,6 +53,7 @@ class MarkerContainer extends Stack { class _RenderMarkerContainer extends RenderStack { late MarkerContainer container; late MapController controller; + late SfMapsThemeData themeData; GlobalKey? tooltipKey; IndexedWidgetBuilder? markerTooltipBuilder; MapShapeSublayer? sublayer; @@ -880,9 +885,7 @@ class MapMarker extends SingleChildRenderObjectWidget { iconStrokeColor: iconStrokeColor, iconStrokeWidth: iconStrokeWidth, iconType: iconType, - themeData: SfMapsTheme.of(context)!, marker: this, - context: context, ); } @@ -899,7 +902,6 @@ class MapMarker extends SingleChildRenderObjectWidget { ..iconStrokeColor = iconStrokeColor ..iconStrokeWidth = iconStrokeWidth ..iconType = iconType - ..themeData = SfMapsTheme.of(context)! ..marker = this; } } @@ -916,8 +918,6 @@ class _RenderMapMarker extends RenderProxyBox required Color? iconStrokeColor, required double? iconStrokeWidth, required MapIconType iconType, - required SfMapsThemeData themeData, - required BuildContext context, required this.marker, }) : _longitude = longitude, _latitude = latitude, @@ -927,13 +927,10 @@ class _RenderMapMarker extends RenderProxyBox _iconColor = iconColor, _iconStrokeColor = iconStrokeColor, _iconStrokeWidth = iconStrokeWidth, - _iconType = iconType, - _themeData = themeData, - _theme = Theme.of(context) { + _iconType = iconType { _tapGestureRecognizer = TapGestureRecognizer()..onTapUp = _handleTapUp; } - final ThemeData _theme; final Size _defaultMarkerSize = const Size(14.0, 14.0); late TapGestureRecognizer _tapGestureRecognizer; @@ -1037,18 +1034,6 @@ class _RenderMapMarker extends RenderProxyBox } } - SfMapsThemeData get themeData => _themeData; - SfMapsThemeData _themeData; - set themeData(SfMapsThemeData value) { - if (_themeData == value) { - return; - } - _themeData = value; - if (child == null) { - markNeedsPaint(); - } - } - void _handleTapUp(TapUpDetails details) { _handleInteraction(); } @@ -1165,11 +1150,14 @@ class _RenderMapMarker extends RenderProxyBox } Paint? _getBorderPaint() { + final _RenderMarkerContainer markerContainer = + parent! as _RenderMarkerContainer; + final SfMapsThemeData themeData = markerContainer.themeData; if (_iconStrokeWidth != null && _iconStrokeWidth! > 0) { return Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _iconStrokeWidth ?? _themeData.markerIconStrokeWidth - ..color = _iconStrokeColor ?? _themeData.markerIconStrokeColor!; + ..strokeWidth = _iconStrokeWidth ?? themeData.markerIconStrokeWidth + ..color = _iconStrokeColor ?? themeData.markerIconStrokeColor!; } return null; @@ -1177,17 +1165,15 @@ class _RenderMapMarker extends RenderProxyBox @override void paint(PaintingContext context, Offset offset) { + final _RenderMarkerContainer markerContainer = + parent! as _RenderMarkerContainer; if (child == null) { core.paint( canvas: context.canvas, rect: paintBounds, shapeType: _getEffectiveShapeType(), paint: Paint() - ..color = _iconColor ?? - _themeData.markerIconColor ?? - (_theme.brightness == Brightness.light - ? const Color.fromRGBO(98, 0, 238, 1) - : const Color.fromRGBO(187, 134, 252, 1)), + ..color = _iconColor ?? markerContainer.themeData.markerIconColor!, borderPaint: _getBorderPaint()); } else { context.paintChild(child!, offset); diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart index d1d6e75ae..b0f76b1a3 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart @@ -26,6 +26,7 @@ import '../elements/marker.dart'; import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../layer/vector_layers.dart'; +import '../theme.dart'; import '../utils.dart'; /// The source that maps the data source with the shape file and provides @@ -1215,6 +1216,7 @@ class _GeoJSONLayerState extends State MapController? _controller; Widget _buildGeoJSONLayer(SfMapsThemeData themeData, bool isDesktop) { + final ThemeData theme = Theme.of(context); Widget current = ClipRect( child: Stack( children: [ @@ -1242,6 +1244,7 @@ class _GeoJSONLayerState extends State MarkerContainer( controller: _controller!, markerTooltipBuilder: widget.markerTooltipBuilder, + themeData: themeData, sublayer: widget.sublayerAncestor, ancestor: ancestor, children: _markers) @@ -1271,9 +1274,9 @@ class _GeoJSONLayerState extends State source: widget.source, mapDataSource: shapeFileData.mapDataSource, settings: widget.dataLabelSettings, - effectiveTextStyle: Theme.of(context).textTheme.bodySmall!.merge( - widget.dataLabelSettings.textStyle ?? - themeData.dataLabelTextStyle), + effectiveTextStyle: theme.textTheme.bodySmall! + .merge(themeData.dataLabelTextStyle) + .merge(widget.dataLabelSettings.textStyle), themeData: themeData, dataLabelAnimationController: dataLabelAnimationController, ), @@ -1355,69 +1358,63 @@ class _GeoJSONLayerState extends State SfMapsThemeData _updateThemeData(BuildContext context, ThemeData themeData, SfMapsThemeData mapsThemeData) { - final bool isLightTheme = mapsThemeData.brightness == Brightness.light; + final SfMapsThemeData effectiveThemeData = themeData.useMaterial3 + ? SfMapsThemeDataM3(context) + : SfMapsThemeDataM2(context); return mapsThemeData.copyWith( layerColor: widget.color ?? (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(198, 198, 198, 1) - : const Color.fromRGBO(71, 71, 71, 1)) - : mapsThemeData.layerColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.11) - : themeData.colorScheme.onSurface.withOpacity(0.24))), + ? (themeData.useMaterial3 + ? (effectiveThemeData as SfMapsThemeDataM3).subLayerColor + : (effectiveThemeData as SfMapsThemeDataM2).subLayerColor) + : mapsThemeData.layerColor ?? effectiveThemeData.layerColor), layerStrokeColor: widget.strokeColor ?? (isSublayer - ? (isLightTheme - ? const Color.fromRGBO(145, 145, 145, 1) - : const Color.fromRGBO(133, 133, 133, 1)) + ? (themeData.useMaterial3 + ? (effectiveThemeData as SfMapsThemeDataM3) + .subLayerStrokeColor + : (effectiveThemeData as SfMapsThemeDataM2) + .subLayerStrokeColor) : mapsThemeData.layerStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.18) - : themeData.colorScheme.onSurface.withOpacity(0.43))), + effectiveThemeData.layerStrokeColor), layerStrokeWidth: widget.strokeWidth ?? (isSublayer - ? (isLightTheme ? 0.5 : 0.25) + ? themeData.useMaterial3 + ? (effectiveThemeData as SfMapsThemeDataM3) + .subLayerStrokeWidth + : (effectiveThemeData as SfMapsThemeDataM2) + .subLayerStrokeWidth : mapsThemeData.layerStrokeWidth), shapeHoverStrokeWidth: mapsThemeData.shapeHoverStrokeWidth ?? mapsThemeData.layerStrokeWidth, legendTextStyle: themeData.textTheme.bodySmall! .copyWith( color: themeData.textTheme.bodySmall!.color!.withOpacity(0.87)) - .merge(widget.legend?.textStyle ?? mapsThemeData.legendTextStyle), - markerIconColor: mapsThemeData.markerIconColor ?? - (isLightTheme - ? const Color.fromRGBO(98, 0, 238, 1) - : const Color.fromRGBO(187, 134, 252, 1)), + .merge(mapsThemeData.legendTextStyle) + .merge(widget.legend?.textStyle), + markerIconColor: + mapsThemeData.markerIconColor ?? effectiveThemeData.markerIconColor, bubbleColor: widget.bubbleSettings.color ?? mapsThemeData.bubbleColor ?? - (isLightTheme - ? const Color.fromRGBO(98, 0, 238, 0.5) - : const Color.fromRGBO(187, 134, 252, 0.8)), + effectiveThemeData.bubbleColor, bubbleStrokeColor: widget.bubbleSettings.strokeColor ?? mapsThemeData.bubbleStrokeColor ?? - Colors.transparent, + effectiveThemeData.bubbleStrokeColor, bubbleStrokeWidth: widget.bubbleSettings.strokeWidth ?? mapsThemeData.bubbleStrokeWidth, bubbleHoverStrokeWidth: mapsThemeData.bubbleHoverStrokeWidth ?? mapsThemeData.bubbleStrokeWidth, selectionColor: widget.selectionSettings.color ?? mapsThemeData.selectionColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.53) - : themeData.colorScheme.onSurface.withOpacity(0.85)), + effectiveThemeData.selectionColor, selectionStrokeColor: widget.selectionSettings.strokeColor ?? mapsThemeData.selectionStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onPrimary.withOpacity(0.29) - : themeData.colorScheme.surface.withOpacity(0.56)), + effectiveThemeData.selectionStrokeColor, selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? mapsThemeData.selectionStrokeWidth, tooltipColor: widget.tooltipSettings.color ?? mapsThemeData.tooltipColor ?? - (isLightTheme - ? const Color.fromRGBO(117, 117, 117, 1) - : const Color.fromRGBO(245, 245, 245, 1)), + effectiveThemeData.tooltipColor, tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? mapsThemeData.tooltipStrokeColor, tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? @@ -1426,14 +1423,10 @@ class _GeoJSONLayerState extends State mapsThemeData.tooltipBorderRadius.resolve(Directionality.of(context)), toggledItemColor: widget.legend?.toggledItemColor ?? mapsThemeData.toggledItemColor ?? - (isLightTheme - ? themeData.colorScheme.onPrimary - : themeData.colorScheme.onSurface.withOpacity(0.09)), + effectiveThemeData.toggledItemColor, toggledItemStrokeColor: widget.legend?.toggledItemStrokeColor ?? mapsThemeData.toggledItemStrokeColor ?? - (isLightTheme - ? themeData.colorScheme.onSurface.withOpacity(0.37) - : themeData.colorScheme.onSurface.withOpacity(0.17)), + effectiveThemeData.toggledItemStrokeColor, toggledItemStrokeWidth: widget.legend?.toggledItemStrokeWidth ?? mapsThemeData.toggledItemStrokeWidth, ); diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart index 037bb97a1..a294c29ab 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart @@ -13,6 +13,7 @@ import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../layer/vector_layers.dart'; import '../layer/zoomable.dart'; +import '../theme.dart'; import '../utils.dart'; Offset _pixelFromLatLng(MapLatLng latLng, double scale) { @@ -53,7 +54,7 @@ class _MapTileCoordinate { String toString() => '_MapTileCoordinate($x, $y, $z)'; @override - bool operator ==(dynamic other) { + bool operator ==(Object other) { if (other is _MapTileCoordinate) { return x == other.x && y == other.y && z == other.z; } @@ -580,6 +581,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { MarkerContainer( markerTooltipBuilder: widget.markerTooltipBuilder, controller: _controller!, + themeData: _mapsThemeData, children: _markers, ), ]); @@ -748,6 +750,9 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); + final SfMapsThemeData effectiveMapsThemeData = themeData.useMaterial3 + ? SfMapsThemeDataM3(context) + : SfMapsThemeDataM2(context); _isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || @@ -756,9 +761,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { _mapsThemeData = _mapsThemeData.copyWith( tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor ?? - (_mapsThemeData.brightness == Brightness.light - ? const Color.fromRGBO(117, 117, 117, 1) - : const Color.fromRGBO(245, 245, 245, 1)), + effectiveMapsThemeData.tooltipColor, tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? _mapsThemeData.tooltipStrokeColor, tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart index bb42e2db1..89352d755 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart @@ -762,7 +762,7 @@ class _MapLineLayerState extends State<_MapLineLayer> lines: widget.lines, animation: widget.animation, color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light + (themeData.brightness == Brightness.light ? themeData.colorScheme.onSurface.withOpacity(0.44) : themeData.colorScheme.onSurface.withOpacity(0.78)), width: widget.width, @@ -1825,7 +1825,7 @@ class _MapArcLayerState extends State<_MapArcLayer> arcs: widget.arcs, animation: widget.animation, color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light + (themeData.brightness == Brightness.light ? themeData.colorScheme.onSurface.withOpacity(0.44) : themeData.colorScheme.onSurface.withOpacity(0.78)), width: widget.width, @@ -2906,7 +2906,7 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> polylines: widget.polylines, animation: widget.animation, color: widget.color ?? - (_mapsThemeData.brightness == Brightness.light + (themeData.brightness == Brightness.light ? themeData.colorScheme.onSurface.withOpacity(0.44) : themeData.colorScheme.onSurface.withOpacity(0.78)), width: widget.width, @@ -3946,14 +3946,14 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> color: widget.color ?? (widget.fillType == _VectorFillType.inner ? const Color.fromRGBO(51, 153, 144, 1) - : (_mapsThemeData.brightness == Brightness.light + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(3, 3, 3, 0.15) : const Color.fromRGBO(0, 0, 0, 0.2))), strokeWidth: widget.strokeWidth, strokeColor: widget.strokeColor ?? (widget.fillType == _VectorFillType.inner ? const Color.fromRGBO(51, 153, 144, 1) - : (_mapsThemeData.brightness == Brightness.light + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(98, 0, 238, 1) : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, @@ -5120,14 +5120,14 @@ class _MapCircleLayerState extends State<_MapCircleLayer> color: widget.color ?? (widget.fillType == _VectorFillType.inner ? const Color.fromRGBO(51, 153, 144, 1) - : (_mapsThemeData.brightness == Brightness.light + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(3, 3, 3, 0.15) : const Color.fromRGBO(0, 0, 0, 0.2))), strokeWidth: widget.strokeWidth, strokeColor: widget.strokeColor ?? (widget.fillType == _VectorFillType.inner ? const Color.fromRGBO(51, 153, 144, 1) - : (_mapsThemeData.brightness == Brightness.light + : (themeData.brightness == Brightness.light ? const Color.fromRGBO(98, 0, 238, 1) : const Color.fromRGBO(187, 134, 252, 0.5))), tooltipBuilder: widget.tooltipBuilder, diff --git a/packages/syncfusion_flutter_maps/pubspec.yaml b/packages/syncfusion_flutter_maps/pubspec.yaml index 931824750..291eee25c 100644 --- a/packages/syncfusion_flutter_maps/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_maps description: A Flutter Maps library for creating beautiful, interactive, and customizable maps from shape files or WMTS services to visualize the geographical area. -version: 23.2.5 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_maps environment: diff --git a/packages/syncfusion_flutter_pdf/CHANGELOG.md b/packages/syncfusion_flutter_pdf/CHANGELOG.md index b90a16d47..cf0eb87d0 100644 --- a/packages/syncfusion_flutter_pdf/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdf/CHANGELOG.md @@ -1,5 +1,69 @@ ## Unreleased +**Breaking changes** + +* The `sign` method in the `IPdfExternalSigner` class has been changed to an asynchronous type, and the `signSync` method has been added for synchronous signing. + +**Features** + +* Provided support for importing and exporting annotations in the PDF document. + +* Provided support for asynchronous external signing in the PDF document. + +## [24.2.8] - 02/27/2024 + +**Bugs** + +* Resolved the issue where extracting text returns incorrect results for a specific PDF document. + +* Invalid font name error will no longer occur when extracting text from specific PDF document. + +## [24.2.5] - 02/13/2024 + +**Bugs** + +* Extracting text lines will no longer return incorrect results for specific PDF documents. + +## [24.2.4] - 02/06/2024 + +**Bugs** + +* Find text related issues are now resolved in PDF documents. + +* Extract text is now working properly in specific PDF document. + +## [24.2.3] - 01/31/2024 + +**Features** + +* Provided support to check and uncheck the check box field items. + +## [24.1.46] - 01/17/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.45] - 01/09/2024 + +**Bugs** + +* Preservation failure no longer occurs in Mac PDF viewer while saving specific encrypted PDF document. + +## [24.1.44] - 01/03/2023 + +**Bugs** + +* Null check error will no longer occur while removing pages from the PDF document. + +## [24.1.43] - 12/27/2023 + +**Bugs** + +* Preservation issue will no longer occur while flattening text box field in specific PDF document. + +## [24.1.41] - 12/18/2023 + **Features** * Provided support for text markup and popup annotations. diff --git a/packages/syncfusion_flutter_pdf/example/pubspec.yaml b/packages/syncfusion_flutter_pdf/example/pubspec.yaml index 2b9e89ae0..3c62055ca 100644 --- a/packages/syncfusion_flutter_pdf/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/example/pubspec.yaml @@ -8,8 +8,8 @@ environment: dependencies: flutter: sdk: flutter - path_provider: ^2.0.1 - open_file: ^3.1.0 + path_provider: ^2.1.2 + open_file: ^3.3.2 syncfusion_flutter_pdf: path: ../ diff --git a/packages/syncfusion_flutter_pdf/lib/pdf.dart b/packages/syncfusion_flutter_pdf/lib/pdf.dart index c69babf48..770a025e6 100644 --- a/packages/syncfusion_flutter_pdf/lib/pdf.dart +++ b/packages/syncfusion_flutter_pdf/lib/pdf.dart @@ -24,7 +24,9 @@ export 'src/pdf/implementation/annotations/enum.dart' HttpMethod, PdfTextMarkupAnnotationType, PdfPopupIcon, - PdfAnnotationFlags; + PdfAnnotationFlags, + PdfAnnotationDataFormat, + PdfAnnotationExportType; export 'src/pdf/implementation/annotations/pdf_action_annotation.dart' show PdfLinkAnnotation, PdfActionLinkAnnotation, PdfActionAnnotation; export 'src/pdf/implementation/annotations/pdf_annotation.dart' diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart index 2525b5851..3abb8acbf 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/enum.dart @@ -67,6 +67,48 @@ enum PdfAnnotationTypes { noAnnotation } +/// Specifies the annotation export types. +enum PdfAnnotationExportType { + /// Line annotation type. + lineAnnotation, + + /// Circle annotation type. + circleAnnotation, + + /// Rectangle annotation type. + rectangleAnnotation, + + /// Polygon annotation type. + polygonAnnotation, + + /// Highlight annotation type. + highlightAnnotation, + + /// Underline annotation type. + underlineAnnotation, + + /// StrikeOut annotation type. + strikeOutAnnotation, + + /// Squiggly annotation type. + squigglyAnnotation, + + /// Popup annotation type. + popupAnnotation +} + +/// Specifies the format of Export or Import data. +enum PdfAnnotationDataFormat { + /// Specifies FDF file format. + fdf, + + /// Specifies XFDF file format. + xfdf, + + /// Specifies JSON file format. + json, +} + /// Specifies the available styles for a field border. enum PdfBorderStyle { /// A solid rectangle surrounding the annotation. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart index 611c3a0a0..d1e2ee659 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_action_annotation.dart @@ -92,17 +92,16 @@ abstract class PdfLinkAnnotation extends PdfAnnotation implements IPdfWrapper { /// [PdfLinkAnnotation] helper class PdfLinkAnnotationHelper extends PdfAnnotationHelper { /// internal constructor - PdfLinkAnnotationHelper(PdfLinkAnnotation linkAnnotation, Rect? bounds) - : super(linkAnnotation) { + PdfLinkAnnotationHelper( + PdfLinkAnnotation super.linkAnnotation, Rect? bounds) { initializeAnnotation(bounds: bounds); dictionary!.setProperty(PdfName(PdfDictionaryProperties.subtype), PdfName(PdfDictionaryProperties.link)); } /// internal constructor - PdfLinkAnnotationHelper.load(PdfLinkAnnotation linkAnnotation, - PdfDictionary dictionary, PdfCrossTable crossTable) - : super(linkAnnotation) { + PdfLinkAnnotationHelper.load(PdfLinkAnnotation super.linkAnnotation, + PdfDictionary dictionary, PdfCrossTable crossTable) { initializeExistingAnnotation(dictionary, crossTable); } } @@ -133,10 +132,10 @@ class PdfActionLinkAnnotationHelper extends PdfLinkAnnotationHelper { /// internal constructor PdfActionLinkAnnotationHelper.load( - PdfActionLinkAnnotation actionLinkAnnotation, - PdfDictionary dictionary, - PdfCrossTable crossTable) - : super.load(actionLinkAnnotation, dictionary, crossTable); + PdfActionLinkAnnotation super.actionLinkAnnotation, + super.dictionary, + super.crossTable) + : super.load(); } /// Represents the annotation with associated action. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart index c7f80ddab..036f712ca 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation.dart @@ -528,7 +528,7 @@ class PdfAnnotationHelper { /// Gets the annotation flags. List get annotationFlags { if (isLoadedAnnotation && flag == null) { - flag ??= obtainAnnotationFlags(); + flag ??= obtainAnnotationFlags(getFlagValue()); } return flag ??= []; } @@ -662,15 +662,18 @@ class PdfAnnotationHelper { .setProperty(PdfName(PdfDictionaryProperties.border), border); } } - final PdfRectangle nativeRectangle = _obtainNativeRectangle(); - if (annotationInnerColor != null && - !annotationInnerColor!.isEmpty && - PdfColorHelper.getHelper(annotationInnerColor!).alpha != 0.0) { - dictionary!.setProperty(PdfName(PdfDictionaryProperties.ic), - PdfColorHelper.toArray(annotationInnerColor!)); + if (base is! PdfLinkAnnotation || + (base is PdfLinkAnnotation && !isLoadedAnnotation)) { + final PdfRectangle nativeRectangle = _obtainNativeRectangle(); + if (annotationInnerColor != null && + !annotationInnerColor!.isEmpty && + PdfColorHelper.getHelper(annotationInnerColor!).alpha != 0.0) { + dictionary!.setProperty(PdfName(PdfDictionaryProperties.ic), + PdfColorHelper.toArray(annotationInnerColor!)); + } + dictionary!.setProperty(PdfName(PdfDictionaryProperties.rect), + PdfArray.fromRectangle(nativeRectangle)); } - dictionary!.setProperty(PdfName(PdfDictionaryProperties.rect), - PdfArray.fromRectangle(nativeRectangle)); } PdfRectangle _obtainNativeRectangle() { @@ -765,7 +768,8 @@ class PdfAnnotationHelper { name.name == PdfDictionaryProperties.squiggly || name.name == PdfDictionaryProperties.underline || name.name == PdfDictionaryProperties.strikeOut || - name.name == PdfDictionaryProperties.text) { + name.name == PdfDictionaryProperties.text || + name.name == PdfDictionaryProperties.link) { catalog.beginSaveList!.add(dictionaryBeginSave); catalog.modify(); } @@ -922,41 +926,46 @@ class PdfAnnotationHelper { } // Gets the Author. - String? _obtainAuthor() { - String? author; + String _obtainAuthor() { + String author = ''; if (dictionary!.containsKey(PdfDictionaryProperties.author)) { - final PdfString? tempAuthor = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.author]) - as PdfString?; - if (tempAuthor != null) { - author = tempAuthor.value; + final IPdfPrimitive? tempAuthor = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.author]); + if (tempAuthor != null && + tempAuthor is PdfString && + tempAuthor.value != null) { + author = tempAuthor.value!; } } else if (dictionary!.containsKey(PdfDictionaryProperties.t)) { - final PdfString? tempAuthor = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.t]) - as PdfString?; - if (tempAuthor != null) { - author = tempAuthor.value; + final IPdfPrimitive? tempAuthor = + PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.t]); + if (tempAuthor != null && + tempAuthor is PdfString && + tempAuthor.value != null) { + author = tempAuthor.value!; } } return author; } // Gets the Subject. - String? _obtainSubject() { - String? subject; + String _obtainSubject() { + String subject = ''; if (dictionary!.containsKey(PdfDictionaryProperties.subject)) { - final PdfString? tempSubject = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.subject]) as PdfString?; - if (tempSubject != null) { - subject = tempSubject.value; + final IPdfPrimitive? tempSubject = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.subject]); + if (tempSubject != null && + tempSubject is PdfString && + tempSubject.value != null) { + subject = tempSubject.value!; } } else if (dictionary!.containsKey(PdfDictionaryProperties.subj)) { - final PdfString? tempSubject = - PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.subj]) - as PdfString?; - if (tempSubject != null) { - subject = tempSubject.value; + final IPdfPrimitive? tempSubject = + PdfCrossTable.dereference(dictionary![PdfDictionaryProperties.subj]); + if (tempSubject != null && + tempSubject is PdfString && + tempSubject.value != null) { + subject = tempSubject.value!; } } return subject; @@ -1980,7 +1989,7 @@ class PdfAnnotationHelper { if (helper.flag != null) { int flagValue = 0; for (int i = 0; i < helper.flag!.length; i++) { - flagValue |= helper.getAnnotationFlagsValue(helper.flag![i]); + flagValue |= getAnnotationFlagsValue(helper.flag![i]); } helper.dictionary!.setNumber(PdfDictionaryProperties.f, flagValue); } @@ -2005,24 +2014,23 @@ class PdfAnnotationHelper { } /// Internal method. - List obtainAnnotationFlags() { - final int? flagValue = getFlagValue(); - flag ??= []; + static List obtainAnnotationFlags(int? flagValue) { + final List flags = []; if (flagValue != null) { for (final PdfAnnotationFlags flag in PdfAnnotationFlags.values) { if (flagValue == 0) { - return this.flag!..add(flag); + return flags..add(flag); } if (getAnnotationFlagsValue(flag) & flagValue != 0) { - this.flag!.add(flag); + flags.add(flag); } } } - return flag!; + return flags; } /// internal method - int getAnnotationFlagsValue(PdfAnnotationFlags value) { + static int getAnnotationFlagsValue(PdfAnnotationFlags value) { switch (value) { case PdfAnnotationFlags.defaultFlag: return 0; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart index 70aa068e9..371cee6e2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_annotation_collection.dart @@ -361,7 +361,7 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { dictionary, crossTable, PdfDictionaryProperties.subtype, true)! as PdfName; final PdfAnnotationTypes type = - _getAnnotationType(name, dictionary, crossTable); + getAnnotationType(name, dictionary, crossTable); final PdfArray? rectValue = PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.rect]) as PdfArray?; @@ -435,7 +435,7 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { } /// Gets the type of the annotation. - PdfAnnotationTypes _getAnnotationType( + static PdfAnnotationTypes getAnnotationType( PdfName name, PdfDictionary dictionary, PdfCrossTable? crossTable) { final String str = name.name!; PdfAnnotationTypes type = PdfAnnotationTypes.noAnnotation; @@ -610,7 +610,7 @@ class PdfAnnotationCollectionHelper extends PdfObjectCollectionHelper { return annot; } - bool _findAnnotation(PdfArray? arr) { + static bool _findAnnotation(PdfArray? arr) { if (arr == null) { return false; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart index 4ec885fd5..2f93a18b7 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_popup_annotation.dart @@ -203,7 +203,7 @@ class PdfPopupAnnotationHelper extends PdfAnnotationHelper { return PdfPopupIcon.insert; case 'key': return PdfPopupIcon.key; - case 'newParagraph': + case 'newparagraph': return PdfPopupIcon.newParagraph; case 'paragraph': return PdfPopupIcon.paragraph; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart index e199679b7..31dfeeab8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_text_markup_annotation.dart @@ -560,10 +560,11 @@ class PdfTextMarkupAnnotationHelper extends PdfAnnotationHelper { for (int i = 0; i < mPathPoints.length; i++, x += length) { mPathPoints[i] = Offset(x, ((height - location) + zigZag) - (height * 0.02)); - if (zigZag == 0) + if (zigZag == 0) { zigZag = location; - else + } else { zigZag = 0; + } } PdfPathHelper.getHelper(path).addLines(mPathPoints); return path; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart index 992fd9cb8..42281b075 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_reader.dart @@ -37,13 +37,13 @@ class CompressedStreamReader { DecompressorHuffmanTree? _currentDistanceTree; /// internal field - static const List def_huffman_dyntree_repeat_bits = [2, 3, 7]; + static const List defHuffmanDyntreeRepeatBits = [2, 3, 7]; /// internal field - static const List def_huffman_dyntree_repeat_minimums = [3, 3, 11]; + static const List defHuffmanDyntreeRepeatMinimums = [3, 3, 11]; /// internal field - static const List def_huffman_repeat_length_base = [ + static const List defHuffmanRepeatLengthBase = [ 3, 4, 5, @@ -76,7 +76,7 @@ class CompressedStreamReader { ]; /// internal field - static const List def_huffman_repeat_length_extension = [ + static const List defHuffmanRepeatLengthExtension = [ 0, 0, 0, @@ -109,7 +109,7 @@ class CompressedStreamReader { ]; /// internal field - static const List def_huffman_repeat_distanse_base = [ + static const List defHuffmanRepeatDistanseBase = [ 1, 2, 3, @@ -143,7 +143,7 @@ class CompressedStreamReader { ]; /// internal field - static const List def_huffman_repeat_distanse_extension = [ + static const List defHuffmanRepeatDistanseExtension = [ 0, 0, 0, @@ -177,16 +177,16 @@ class CompressedStreamReader { ]; /// internal field - static const int def_huffman_repeat_max = 258; + static const int defHuffmanRepeatMax = 258; /// internal field - static const int def_huffman_end_block = 256; + static const int defHuffmanEndBlock = 256; /// internal field - static const int def_huffman_length_minimum_code = 257; + static const int defHuffmanLengthMinimumCode = 257; /// internal field - static const int def_huffman_length_maximum_code = 285; + static const int defHuffmanLengthMaximumCode = 285; /// internal field late int bufferedBits; @@ -323,7 +323,7 @@ class CompressedStreamReader { } arrDecoderCodeLengths[CompressedStreamWriter - .def_huffman_dyntree_codelengths_order[iCurrentCode++]] = + .defHuffmanDyntreeCodeLengthsOrder[iCurrentCode++]] = len.toUnsigned(8); } final DecompressorHuffmanTree treeInternalDecoder = @@ -360,7 +360,7 @@ class CompressedStreamReader { } final int iRepSymbol = symbol - 16; - final int bits = def_huffman_dyntree_repeat_bits[iRepSymbol]; + final int bits = defHuffmanDyntreeRepeatBits[iRepSymbol]; int count = _readBits(bits); @@ -368,7 +368,7 @@ class CompressedStreamReader { throw ArgumentError.value(count, 'Wrong dynamic huffman codes.'); } - count += def_huffman_dyntree_repeat_minimums[iRepSymbol]; + count += defHuffmanDyntreeRepeatMinimums[iRepSymbol]; if (iCurrentCode + count > iResultingCodeLengthsCount) { throw ArgumentError.value(iCurrentCode, 'Wrong dynamic huffman codes.'); @@ -660,32 +660,32 @@ class CompressedStreamReader { int free = _maxValue - (_dataLength - _currentPosition); bool dataRead = false; int symbol = 0; - while (free >= def_huffman_repeat_max) { + while (free >= defHuffmanRepeatMax) { while (((symbol = _currentLengthTree!.unpackSymbol(this)) & ~0xff) == 0) { _blockBuffer![_dataLength++ % _maxValue] = symbol.toUnsigned(8); dataRead = true; - if (--free < def_huffman_repeat_max) { + if (--free < defHuffmanRepeatMax) { return true; } } - if (symbol < def_huffman_length_minimum_code) { - if (symbol < def_huffman_end_block) { + if (symbol < defHuffmanLengthMinimumCode) { + if (symbol < defHuffmanEndBlock) { throw ArgumentError.value(symbol, 'Illegal code.'); } _canReadMoreData = _decodeBlockHeader(); return dataRead | _canReadMoreData; } - if (symbol > def_huffman_length_maximum_code) { + if (symbol > defHuffmanLengthMaximumCode) { throw ArgumentError.value(symbol, 'Illegal repeat code length.'); } - int iRepeatLength = def_huffman_repeat_length_base[ - symbol - def_huffman_length_minimum_code]; + int iRepeatLength = + defHuffmanRepeatLengthBase[symbol - defHuffmanLengthMinimumCode]; - int iRepeatExtraBits = def_huffman_repeat_length_extension[ - symbol - def_huffman_length_minimum_code]; + int iRepeatExtraBits = + defHuffmanRepeatLengthExtension[symbol - defHuffmanLengthMinimumCode]; if (iRepeatExtraBits > 0) { final int extra = _readBits(iRepeatExtraBits); @@ -698,11 +698,11 @@ class CompressedStreamReader { // Unpack repeat distance. symbol = _currentDistanceTree!.unpackSymbol(this); - if (symbol < 0 || symbol > def_huffman_repeat_distanse_base.length) { + if (symbol < 0 || symbol > defHuffmanRepeatDistanseBase.length) { throw ArgumentError.value(symbol, 'Wrong distance code.'); } - int iRepeatDistance = def_huffman_repeat_distanse_base[symbol]; - iRepeatExtraBits = def_huffman_repeat_distanse_extension[symbol]; + int iRepeatDistance = defHuffmanRepeatDistanseBase[symbol]; + iRepeatExtraBits = defHuffmanRepeatDistanseExtension[symbol]; if (iRepeatExtraBits > 0) { final int extra = _readBits(iRepeatExtraBits); if (extra < 0) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart index 3a6a5655f..308de9a91 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressed_stream_writer.dart @@ -8,26 +8,26 @@ class CompressedStreamWriter { /// internal constructor CompressedStreamWriter(List outputStream, bool bNoWrap, PdfCompressionLevel? level, bool bCloseStream) { - _treeLiteral = CompressorHuffmanTree( - this, def_huffman_literal_alphabet_length, 257, 15); - _treeDistances = CompressorHuffmanTree( - this, def_huffman_distances_alphabet_length, 1, 15); + _treeLiteral = + CompressorHuffmanTree(this, defHuffmanLiteralAlphabetLength, 257, 15); + _treeDistances = + CompressorHuffmanTree(this, defHuffmanDistancesAlphabetLength, 1, 15); _treeCodeLengths = - CompressorHuffmanTree(this, def_huffman_bitlen_tree_length, 4, 7); - _arrDistancesBuffer = List.filled(def_huffman_buffer_size, 0); - _arrLiteralsBuffer = List.filled(def_huffman_buffer_size, 0); + CompressorHuffmanTree(this, defHuffmanBitlenTreeLength, 4, 7); + _arrDistancesBuffer = List.filled(defHuffmanBufferSize, 0); + _arrLiteralsBuffer = List.filled(defHuffmanBufferSize, 0); _stream = outputStream; _level = level; _bNoWrap = bNoWrap; _bCloseStream = bCloseStream; _dataWindow = List.filled(2 * wsize, 0); - _hashHead = List.filled(hash_size, 0); + _hashHead = List.filled(hashSize, 0); _hashPrevious = List.filled(wsize, 0); _blockStart = _stringStart = 1; - _goodLength = good_length[_getCompressionLevel(level)!]; - _niceLength = nice_length[_getCompressionLevel(level)!]; - _maximumChainLength = max_chain[_getCompressionLevel(level)!]; - _maximumLazySearch = max_lazy[_getCompressionLevel(level)!]; + _goodLength = goodLength[_getCompressionLevel(level)!]; + _niceLength = niceLength[_getCompressionLevel(level)!]; + _maximumChainLength = maxChain[_getCompressionLevel(level)!]; + _maximumLazySearch = maxLazy[_getCompressionLevel(level)!]; if (!bNoWrap) { _writeZLIBHeader(); } @@ -35,31 +35,31 @@ class CompressedStreamWriter { } /// Start template of the zlib header. - static const int def_zlib_header_template = (8 + (7 << 4)) << 8; + static const int defZlibHeaderTemplate = (8 + (7 << 4)) << 8; /// Memory usage level. - static const int default_mem_level = 8; + static const int defaultMemLevel = 8; /// Size of the pending buffer. - static const int def_pending_buffer_size = 1 << (default_mem_level + 8); + static const int defPendingBufferSize = 1 << (defaultMemLevel + 8); /// Size of the buffer for the huffman encoding. - static const int def_huffman_buffer_size = 1 << (default_mem_level + 6); + static const int defHuffmanBufferSize = 1 << (defaultMemLevel + 6); /// Length of the literal alphabet(literal+lengths). - static const int def_huffman_literal_alphabet_length = 286; + static const int defHuffmanLiteralAlphabetLength = 286; /// Distances alphabet length. - static const int def_huffman_distances_alphabet_length = 30; + static const int defHuffmanDistancesAlphabetLength = 30; /// Length of the code-lengths tree. - static const int def_huffman_bitlen_tree_length = 19; + static const int defHuffmanBitlenTreeLength = 19; /// Code of the symbol, than means the end of the block. - static const int def_huffman_endblock_symbol = 256; + static const int defHuffmanEndblockSymbol = 256; /// internal field - static const int too_far = 4096; + static const int tooFar = 4096; /// Maximum window size. static const int wsize = 1 << 15; @@ -68,31 +68,31 @@ class CompressedStreamWriter { static const int wmask = wsize - 1; /// Internal compression engine constant - static const int hash_bits = default_mem_level + 7; + static const int hashBits = defaultMemLevel + 7; /// Internal compression engine constant - static const int hash_size = 1 << hash_bits; + static const int hashSize = 1 << hashBits; /// Internal compression engine constant - static const int hash_mask = hash_size - 1; + static const int hashMask = hashSize - 1; /// Internal compression engine constant - static const int max_match = 258; + static const int maxMatch = 258; /// Internal compression engine constant - static const int min_match = 3; + static const int minMatch = 3; /// Internal compression engine constant - static const int hash_shift = 5; + static const int hashShift = 5; /// Internal compression engine constant - static const int min_lookahead = max_match + min_match + 1; + static const int minLookahead = maxMatch + minMatch + 1; /// Internal compression engine constant - static const int max_dist = wsize - min_lookahead; + static const int maxDist = wsize - minLookahead; /// Internal compression engine constant - static const List good_length = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32]; + static const List goodLength = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32]; /// internal field static const int checkSumBitOffset = 16; @@ -104,7 +104,7 @@ class CompressedStreamWriter { static const int checksumIterationCount = 3800; /// Internal compression engine constant - static const List nice_length = [ + static const List niceLength = [ 0, 8, 16, @@ -118,7 +118,7 @@ class CompressedStreamWriter { ]; /// Internal compression engine constant - static const List max_chain = [ + static const List maxChain = [ 0, 4, 8, @@ -132,7 +132,7 @@ class CompressedStreamWriter { ]; /// internal field - static const List def_reverse_bits = [ + static const List defReverseBits = [ 0, 8, 4, @@ -152,7 +152,7 @@ class CompressedStreamWriter { ]; /// internal field - static const List def_huffman_dyntree_codelengths_order = [ + static const List defHuffmanDyntreeCodeLengthsOrder = [ 16, 17, 18, @@ -175,9 +175,9 @@ class CompressedStreamWriter { ]; /// internal field - static const List max_lazy = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258]; + static const List maxLazy = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258]; - final List _pendingBuffer = List.filled(def_pending_buffer_size, 0); + final List _pendingBuffer = List.filled(defPendingBufferSize, 0); int _pendingBufferLength = 0; late List _arrDistancesBuffer; late List _arrLiteralsBuffer; @@ -216,7 +216,7 @@ class CompressedStreamWriter { static late List _arrDistanceLengths; bool get _needsInput => _inputEnd == _inputOffset; bool get _pendingBufferIsFlushed => _pendingBufferLength == 0; - bool get _huffmanIsFull => _iBufferPosition >= def_huffman_buffer_size; + bool get _huffmanIsFull => _iBufferPosition >= defHuffmanBufferSize; int _maximumLazySearch = 0; int? _getCompressionLevel(PdfCompressionLevel? level) { @@ -242,10 +242,8 @@ class CompressedStreamWriter { /// internal method void initializeStaticLiterals() { if (_arrLiteralCodes == null) { - _arrLiteralCodes = - List.filled(def_huffman_literal_alphabet_length, 0); - _arrLiteralLengths = - List.filled(def_huffman_literal_alphabet_length, 0); + _arrLiteralCodes = List.filled(defHuffmanLiteralAlphabetLength, 0); + _arrLiteralLengths = List.filled(defHuffmanLiteralAlphabetLength, 0); int i = 0; while (i < 144) { _arrLiteralCodes![i] = bitReverse((0x030 + i) << 8); @@ -259,16 +257,16 @@ class CompressedStreamWriter { _arrLiteralCodes![i] = bitReverse(((0x000 - 256) + i) << 9); _arrLiteralLengths[i++] = 7; } - while (i < def_huffman_literal_alphabet_length) { + while (i < defHuffmanLiteralAlphabetLength) { _arrLiteralCodes![i] = bitReverse(((0x0c0 - 280) + i) << 8); _arrLiteralLengths[i++] = 8; } _arrDistanceCodes = - List.filled(def_huffman_distances_alphabet_length, 0); + List.filled(defHuffmanDistancesAlphabetLength, 0); _arrDistanceLengths = - List.filled(def_huffman_distances_alphabet_length, 0); + List.filled(defHuffmanDistancesAlphabetLength, 0); - for (i = 0; i < def_huffman_distances_alphabet_length; i++) { + for (i = 0; i < defHuffmanDistancesAlphabetLength; i++) { _arrDistanceCodes[i] = bitReverse(i << 11); _arrDistanceLengths[i] = 5; } @@ -277,7 +275,7 @@ class CompressedStreamWriter { void _writeZLIBHeader() { // Initialize header. - int iHeaderData = def_zlib_header_template; + int iHeaderData = defZlibHeaderTemplate; // Save compression level. iHeaderData |= ((_getCompressionLevel(_level)! >> 2) & 3) << 6; // Align header. @@ -351,11 +349,11 @@ class CompressedStreamWriter { // Compress, using maximum compression level. bool _compressSlow(bool flush, bool finish) { - if (_lookAhead < min_lookahead && !flush) { + if (_lookAhead < minLookahead && !flush) { return false; } - while (_lookAhead >= min_lookahead || flush) { + while (_lookAhead >= minLookahead || flush) { if (_lookAhead == 0) { if (_matchPreviousAvailable) { _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); @@ -371,30 +369,30 @@ class CompressedStreamWriter { return false; } - if (_stringStart >= 2 * wsize - min_lookahead) { + if (_stringStart >= 2 * wsize - minLookahead) { _slideWindow(); } final int prevMatch = _matchStart; int prevLen = _matchLength; - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { final int hashHead = _insertString(); if (hashHead != 0 && - _stringStart - hashHead <= max_dist && + _stringStart - hashHead <= maxDist && _findLongestMatch(hashHead)) { /// Discard match if too small and too far away. if (_matchLength <= 5 && - (_matchLength == min_match) && - _stringStart - _matchStart > too_far) { - _matchLength = min_match - 1; + (_matchLength == minMatch) && + _stringStart - _matchStart > tooFar) { + _matchLength = minMatch - 1; } } } /// Previous match was better. - if (prevLen >= min_match && _matchLength <= prevLen) { + if (prevLen >= minMatch && _matchLength <= prevLen) { _huffmanTallyDist(_stringStart - 1 - prevMatch, prevLen); prevLen -= 2; @@ -402,7 +400,7 @@ class CompressedStreamWriter { _stringStart++; _lookAhead--; - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { _insertString(); } } while (--prevLen > 0); @@ -410,7 +408,7 @@ class CompressedStreamWriter { _stringStart++; _lookAhead--; _matchPreviousAvailable = false; - _matchLength = min_match - 1; + _matchLength = minMatch - 1; } else { if (_matchPreviousAvailable) { _huffmanTallyLit(_dataWindow![_stringStart - 1] & 0xff); @@ -441,11 +439,11 @@ class CompressedStreamWriter { // Compress with a maximum speed. bool _compressFast(bool flush, bool finish) { - if (_lookAhead < min_lookahead && !flush) { + if (_lookAhead < minLookahead && !flush) { return false; } - while (_lookAhead >= min_lookahead || flush) { + while (_lookAhead >= minLookahead || flush) { if (_lookAhead == 0) { _huffmanFlushBlock( _dataWindow, _blockStart, _stringStart - _blockStart, finish); @@ -453,13 +451,13 @@ class CompressedStreamWriter { return false; } - if (_stringStart > 2 * wsize - min_lookahead) { + if (_stringStart > 2 * wsize - minLookahead) { _slideWindow(); } int hashHead; - if (_lookAhead >= min_match && + if (_lookAhead >= minMatch && (hashHead = _insertString()) != 0 && - _stringStart - hashHead <= max_dist && + _stringStart - hashHead <= maxDist && _findLongestMatch(hashHead)) { if (_huffmanTallyDist(_stringStart - _matchStart, _matchLength)) { final bool lastBlock = finish && _lookAhead == 0; @@ -468,7 +466,7 @@ class CompressedStreamWriter { _blockStart = _stringStart; } _lookAhead -= _matchLength; - if (_matchLength <= _maximumLazySearch && _lookAhead >= min_match) { + if (_matchLength <= _maximumLazySearch && _lookAhead >= minMatch) { while (--_matchLength > 0) { ++_stringStart; _insertString(); @@ -478,11 +476,11 @@ class CompressedStreamWriter { } else { _stringStart += _matchLength; - if (_lookAhead >= min_match - 1) { + if (_lookAhead >= minMatch - 1) { _updateHash(); } } - _matchLength = min_match - 1; + _matchLength = minMatch - 1; continue; } else { @@ -507,11 +505,11 @@ class CompressedStreamWriter { int scan = _stringStart; int match; int bestEnd = _stringStart + _matchLength; - int bestLen = max(_matchLength, min_match - 1); + int bestLen = max(_matchLength, minMatch - 1); - final int limit = max(_stringStart - max_dist, 0); + final int limit = max(_stringStart - maxDist, 0); - final int strend = _stringStart + max_match - 1; + final int strend = _stringStart + maxMatch - 1; int scanEnd1 = _dataWindow![bestEnd - 1]; int scanEnd = _dataWindow![bestEnd]; @@ -569,7 +567,7 @@ class CompressedStreamWriter { _matchLength = min(bestLen, _lookAhead); - return _matchLength >= min_match; + return _matchLength >= minMatch; } bool _huffmanTallyDist(int dist, int len) { @@ -592,7 +590,7 @@ class CompressedStreamWriter { void _huffmanFlushBlock( List? stored, int storedOffset, int storedLength, bool lastBlock) { - _treeLiteral.codeFrequences[def_huffman_endblock_symbol]++; + _treeLiteral.codeFrequences[defHuffmanEndblockSymbol]++; // Build trees. _treeLiteral.buildTree(); @@ -607,8 +605,7 @@ class CompressedStreamWriter { int blTreeCodes = 4; for (int i = 18; i > blTreeCodes; i--) { - if (_treeCodeLengths! - .codeLengths![def_huffman_dyntree_codelengths_order[i]] > + if (_treeCodeLengths!.codeLengths![defHuffmanDyntreeCodeLengthsOrder[i]] > 0) { blTreeCodes = i + 1; } @@ -621,10 +618,10 @@ class CompressedStreamWriter { _iExtraBits; int staticLen = _iExtraBits; - for (int i = 0; i < def_huffman_literal_alphabet_length; i++) { + for (int i = 0; i < defHuffmanLiteralAlphabetLength; i++) { staticLen += _treeLiteral.codeFrequences[i] * _arrLiteralLengths[i]; } - for (int i = 0; i < def_huffman_distances_alphabet_length; i++) { + for (int i = 0; i < defHuffmanDistancesAlphabetLength; i++) { staticLen += _treeDistances.codeFrequences[i] * _arrDistanceLengths[i]; } if (optLen >= staticLen) { @@ -661,7 +658,7 @@ class CompressedStreamWriter { for (int rank = 0; rank < blTreeCodes; rank++) { pendingBufferWriteBits( _treeCodeLengths! - .codeLengths![def_huffman_dyntree_codelengths_order[rank]], + .codeLengths![defHuffmanDyntreeCodeLengthsOrder[rank]], 3); } @@ -671,9 +668,9 @@ class CompressedStreamWriter { int _insertString() { int match; - final int hash = ((_currentHash << hash_shift) ^ - _dataWindow![_stringStart + (min_match - 1)]) & - hash_mask; + final int hash = ((_currentHash << hashShift) ^ + _dataWindow![_stringStart + (minMatch - 1)]) & + hashMask; _hashPrevious[_stringStart & wmask] = match = _hashHead[hash]; _hashHead[hash] = _stringStart.toSigned(16); @@ -707,7 +704,7 @@ class CompressedStreamWriter { } } - _treeLiteral.writeCodeToStream(def_huffman_endblock_symbol); + _treeLiteral.writeCodeToStream(defHuffmanEndblockSymbol); } int _huffmanDistanceCode(int distance) { @@ -795,11 +792,11 @@ class CompressedStreamWriter { } void _fillWindow() { - if (_stringStart >= wsize + max_dist) { + if (_stringStart >= wsize + maxDist) { _slideWindow(); } - while (_lookAhead < min_lookahead && _inputOffset < _inputEnd) { + while (_lookAhead < minLookahead && _inputOffset < _inputEnd) { int more = 2 * wsize - _lookAhead - _stringStart; if (more > _inputEnd - _inputOffset) { more = _inputEnd - _inputOffset; @@ -811,7 +808,7 @@ class CompressedStreamWriter { _lookAhead += more; } - if (_lookAhead >= min_match) { + if (_lookAhead >= minMatch) { _updateHash(); } } @@ -822,7 +819,7 @@ class CompressedStreamWriter { _stringStart -= wsize; _blockStart -= wsize; - for (int i = 0; i < hash_size; ++i) { + for (int i = 0; i < hashSize; ++i) { final int m = _hashHead[i] & 0xffff; _hashHead[i] = ((m >= wsize) ? (m - wsize) : 0).toSigned(16); } @@ -834,7 +831,7 @@ class CompressedStreamWriter { } void _updateHash() { - _currentHash = (_dataWindow![_stringStart] << hash_shift) ^ + _currentHash = (_dataWindow![_stringStart] << hashShift) ^ _dataWindow![_stringStart + 1]; } @@ -852,7 +849,7 @@ class CompressedStreamWriter { int _pendingBufferFlushBits() { int result = 0; while (_pendingBufferBitsInCache >= 8 && - _pendingBufferLength < def_pending_buffer_size) { + _pendingBufferLength < defPendingBufferSize) { _pendingBuffer[_pendingBufferLength++] = _pendingBufferBitsCache.toUnsigned(8); _pendingBufferBitsCache >>= 8; @@ -892,10 +889,10 @@ class CompressedStreamWriter { /// internal field static int bitReverse(int value) { - return (def_reverse_bits[(value & 15)] << 12 | - def_reverse_bits[((value >> 4) & 15)] << 8 | - def_reverse_bits[((value >> 8) & 15)] << 4 | - def_reverse_bits[(value >> 12)]) + return (defReverseBits[(value & 15)] << 12 | + defReverseBits[((value >> 4) & 15)] << 8 | + defReverseBits[((value >> 8) & 15)] << 4 | + defReverseBits[(value >> 12)]) .toSigned(16); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart index a9832e26a..bc9dcb732 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/compressor_huffman_tree.dart @@ -15,7 +15,7 @@ class CompressorHuffmanTree { } /// internal field - static const List def_reverse_bits = [ + static const List defReverseBits = [ 0, 8, 4, @@ -364,10 +364,10 @@ class CompressorHuffmanTree { } int _bitReverse(int value) { - return (def_reverse_bits[value & 15] << 12 | - def_reverse_bits[(value >> 4) & 15] << 8 | - def_reverse_bits[(value >> 8) & 15] << 4 | - def_reverse_bits[value >> 12]) + return (defReverseBits[value & 15] << 12 | + defReverseBits[(value >> 4) & 15] << 8 | + defReverseBits[(value >> 8) & 15] << 4 | + defReverseBits[value >> 12]) .toSigned(16); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart index 000282298..2bfdd0086 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_file2.dart @@ -1,6 +1,5 @@ -/// internal constructor // ignore_for_file: text_direction_code_point_in_literal - +/// internal constructor class AdobeGlyphList { // Constructor /// internal class diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart index 4b29fdec6..19bdf0a7b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/font_structure.dart @@ -972,7 +972,10 @@ class FontStructure { /// internal method PdfFont? createStandardFont(double size) { - if (_standardFontName != '') { + if (_standardFontName != null && _standardFontName != '') { + if (_standardFontName!.contains('#')) { + _standardFontName = decodeHexFontName(_standardFontName!); + } final PdfFontFamily fontFamily = _getFontFamily(_standardFontName!); final List styles = _getFontStyle(_standardFontName!); if (styles.contains(PdfFontStyle.bold) && @@ -2493,6 +2496,7 @@ class FontStructure { String encodedText = textToDecode; this.isSameFont = isSameFont; bool hasEscapeChar = false; + bool isHex = false; switch (encodedText[0]) { case '(': { @@ -2546,7 +2550,7 @@ class FontStructure { } encodedText = encodedText.substring(1, encodedText.length - 1); while (encodedText.isNotEmpty) { - bool isHex = false; + isHex = false; int textStart = encodedText.indexOf('('); int textEnd = encodedText.indexOf(')'); final int textHexStart = encodedText.indexOf('<'); @@ -2593,6 +2597,7 @@ class FontStructure { final String hexEncodedText = encodedText.substring(1, encodedText.length - 1); decodedText = getHexaDecimalString(hexEncodedText, charcodes); + isHex = true; } break; default: @@ -2604,7 +2609,7 @@ class FontStructure { (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; if (characterMapTable.isNotEmpty) { - decodedText = mapCharactersFromTable(decodedText); + decodedText = mapCharactersFromTable(decodedText, isHex); } else if (differencesDictionary.isNotEmpty) { decodedText = mapDifferences(decodedText); } else if (fontEncoding != '') { @@ -2898,7 +2903,7 @@ class FontStructure { (fontEncoding == 'Identity-H' && containsCmap)) { isMappingDone = true; if (characterMapTable.isNotEmpty) { - listElement = mapCharactersFromTable(listElement); + listElement = mapCharactersFromTable(listElement, isHex); } else if (differencesDictionary.isNotEmpty) { listElement = mapDifferences(listElement); } else if (fontEncoding != '') { @@ -4204,8 +4209,9 @@ class FontStructure { decodedtext = encodedText; final int charPosition = reverseMapTable![decodedtext]!.toInt(); if (differenceTable.isNotEmpty && - differenceTable.containsKey(charPosition)) + differenceTable.containsKey(charPosition)) { zapfPostScript = differenceTable[charPosition]!; + } } else { decodedtext = '\u2708'; zapfPostScript = 'a118'; @@ -4773,7 +4779,7 @@ class FontStructure { /// Takes in the decoded text and maps it with its /// corresponding entry in the CharacterMapTable - String mapCharactersFromTable(String decodedText) { + String mapCharactersFromTable(String decodedText, [bool isHex = false]) { String finalText = ''; bool skip = false; for (int i = 0; i < decodedText.length; i++) { @@ -4799,7 +4805,8 @@ class FontStructure { finalText += mappingString; skip = false; } else if (!characterMapTable.containsKey(character.codeUnitAt(0)) && - !skip) { + !skip && + !isHex) { final List bytes = encodeBigEndian(character); if (bytes[0] != 92) { if (characterMapTable.containsKey(bytes[0])) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart index fe2b0a47c..4505853ab 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/image_renderer.dart @@ -53,6 +53,7 @@ class ImageRenderer { GraphicsObject? _graphicsObject; PdfRecordCollection? _contentElements; PdfPageResources? _resources; + String? _actualText; /// internal field double? currentPageHeight; @@ -234,6 +235,17 @@ class ImageRenderer { if (_skipRendering) { _inlayersCount++; } + if (elements[1].contains('ActualText') && + elements[1].contains('(')) { + _actualText = elements[1].substring( + elements[1].indexOf('(') + 1, + elements[1].lastIndexOf(')')); + const String bigEndianPreambleString = 'þÿ'; + if (_actualText != null && + _actualText!.startsWith(bigEndianPreambleString)) { + _actualText = null; + } + } } } break; @@ -245,6 +257,7 @@ class ImageRenderer { if (_inlayersCount <= 0) { _skipRendering = false; } + _actualText = null; } break; case 'q': @@ -617,7 +630,6 @@ class ImageRenderer { _resources![currentFont!] as FontStructure; structure.isSameFont = _resources!.isSameFont(); structure.fontSize = fontSize; - if (!structure.isEmbedded && structure.font != null && structure.isStandardCJKFont) { @@ -630,6 +642,10 @@ class ImageRenderer { text = structure.decodeTextExtraction( text, _resources!.isSameFont(), retrievedCharCodes); } + if (_actualText != null && _actualText!.isNotEmpty) { + text = _actualText!; + _actualText = null; + } final TextElement element = TextElement(text, documentMatrix); element.fontStyle = structure.fontStyle!; element.fontName = structure.fontName!; @@ -661,6 +677,7 @@ class ImageRenderer { element.substitutedFontsList = _substitutedFontsList; element.wordSpacing = objects.wordSpacing; element.characterSpacing = objects.characterSpacing; + element.isExtractTextData = isExtractLineCollection; final MatrixHelper tempTextMatrix = MatrixHelper(0, 0, 0, 0, 0, 0); tempTextMatrix.type = MatrixTypes.identity; if (_isCurrentPositionChanged) { @@ -790,6 +807,7 @@ class ImageRenderer { element.pageRotation = pageRotation; element.zoomFactor = zoomFactor; element.substitutedFontsList = _substitutedFontsList; + element.isExtractTextData = isExtractLineCollection; if (structure.flags != null) { element.fontFlag = structure.flags!.value!.toInt(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart index 824e4defe..183953ee8 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/parser/content_lexer.dart @@ -146,7 +146,8 @@ class ContentLexer { _resetToken(); while (true) { final String ch = _consumeValue(); - if (_isWhiteSpace(ch) || _isDelimiter(ch)) { + final String data = String.fromCharCode(int.parse(ch)); + if (_isWhiteSpace(ch) || _isDelimiter(data)) { break; } } @@ -260,10 +261,13 @@ class ContentLexer { PdfToken _getHexadecimalString() { int parentLevel = 0; + bool skipParentLevel = false; String ch = String.fromCharCode(int.parse(_consumeValue())); while (true) { if (ch == '<') { - parentLevel++; + if (!skipParentLevel) { + parentLevel++; + } ch = String.fromCharCode(int.parse(_consumeValue())); } else if (ch == '>' && !_isArtifactContentEnds) { if (parentLevel == 0) { @@ -284,6 +288,16 @@ class ContentLexer { } ch = String.fromCharCode(int.parse(_consumeValue())); } + } else if (operatorParams != null && + operatorParams.toString().contains('ActualText') && + ch == '(') { + skipParentLevel = true; + ch = String.fromCharCode(int.parse(_consumeValue())); + } else if (operatorParams != null && + operatorParams.toString().contains('ActualText') && + ch == ')') { + skipParentLevel = false; + ch = String.fromCharCode(int.parse(_consumeValue())); } else { ch = String.fromCharCode(int.parse(_consumeValue())); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart index a646f368f..cfa7a3bf0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/pdf_text_extractor.dart @@ -76,6 +76,7 @@ class PdfTextExtractor { bool _hasBDC = false; Bidi? _bidiInstance; MatrixHelper? _initialTransform; + String? _actualText; //Properties Bidi get _bidi { @@ -243,7 +244,12 @@ class PdfTextExtractor { String resultText = ''; for (int i = startPageIndex; i <= endPageIndex; i++) { final String text = _getText(_document.pages[i]); - resultText = resultText + (i > startPageIndex ? '\r\n' : '') + text; + resultText = resultText + + (i > startPageIndex && + (!_isLayout || (_isLayout && !resultText.endsWith('\n'))) + ? '\r\n' + : '') + + text; } return resultText; } @@ -389,7 +395,6 @@ class PdfTextExtractor { for (int x = 0; x < words.length; x++) { if (pagestring.contains(words[x]) && words[x].isNotEmpty) { glyphs = []; - String tempText = ''; int lastIndex = i; for (int m = i; m < i + words[x].length; m++) { final Glyph tempGlyph = renderer.imageRenderGlyphList[m]; @@ -403,12 +408,8 @@ class PdfTextExtractor { _calculateBounds(glyphBounds), textElement.fontSize, tempGlyph.isRotated); - tempText += textGlyph.text; glyphs.add(textGlyph); lastIndex = m; - if (words[x] == tempText) { - break; - } } Rect? wordBound; dx = renderer.imageRenderGlyphList[i].boundingRect.left; @@ -469,7 +470,12 @@ class PdfTextExtractor { } if (x < words.length - 1 && i <= renderer.imageRenderGlyphList.length - 1 && - renderer.imageRenderGlyphList[i].toUnicode == ' ') { + renderer.imageRenderGlyphList[i].toUnicode == ' ' && + ((renderer.imageRenderGlyphList[i].isRotated && + (rotation == 270 || rotation == 90)) || + renderer.imageRenderGlyphList[i].boundingRect.top + .toInt() == + offsetY)) { i = i + 1; } } else { @@ -1298,12 +1304,27 @@ class PdfTextExtractor { resultantText += '\r\n'; break; } + case 'BDC': + { + if (elements != null && + elements.length > 1 && + elements[1].contains('ActualText') && + elements[1].contains('(')) { + _initializeActualText(elements[1]); + } + break; + } + case 'EMC': + _actualText = null; + break; case 'Tj': case 'TJ': case "'": { - final String? resultText = - _renderTextElement(elements!, token, pageResources, null); + final String? resultText = (_actualText != null && + _actualText!.isNotEmpty) + ? _actualText + : _renderTextElement(elements!, token, pageResources, null); if (resultText != null) { resultantText += resultText; } @@ -1339,7 +1360,6 @@ class PdfTextExtractor { String? currentText = ''; bool hasTj = false; bool hasTm = false; - bool hasTJ = false; _hasBDC = false; String resultantText = ''; double? textLeading = 0; @@ -1378,6 +1398,7 @@ class PdfTextExtractor { } case 'Tm': { + hasTm = true; final double a = double.tryParse(elements![0])!; final double b = double.tryParse(elements[1])!; final double c = double.tryParse(elements[2])!; @@ -1413,7 +1434,9 @@ class PdfTextExtractor { final int locationY = (current - prev) ~/ 10; if ((current != prev) && hasTm && - (locationY < 0 || locationY >= 1)) { + (locationY < 0 || locationY >= 1) && + resultantText.isNotEmpty && + !resultantText.endsWith('\n')) { resultantText += '\r\n'; hasTm = false; } @@ -1423,9 +1446,19 @@ class PdfTextExtractor { case 'BDC': { _hasBDC = true; - _hasET = true; + if (elements != null && + elements.length > 1 && + elements[1].contains('ActualText') && + elements[1].contains('(')) { + _initializeActualText(elements[1]); + } else { + _hasET = true; + } break; } + case 'EMC': + _actualText = null; + break; case 'TD': { textLeading = double.tryParse(elements![1]); @@ -1465,7 +1498,8 @@ class PdfTextExtractor { _tempBoundingRectangle = Rect.zero; _hasBDC = false; } - if ((_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) > + if ((_textLineMatrix!.offsetX - _currentTextMatrix!.offsetX) + .abs() > 0 && !spaceBetweenWord && hasTj) { @@ -1527,27 +1561,38 @@ class PdfTextExtractor { if (difference < 0) { difference = -difference; } - if (spaceBetweenWord) { + if (prevY != 0 && difference >= 1) { + if (resultantText.isNotEmpty && !resultantText.endsWith('\n')) { + resultantText += '\r\n'; + } + } else if (spaceBetweenWord) { if (differenceX > _fontSize!) { differenceX = 0; } + bool isEncoded = true; + if (elements != null) { + final String text = elements.join(); + isEncoded = text[0] != '(' || + (text[0] == '(' && _hasOctalEscape(text)); + } if (currentToken == 'Tj' && - (_hasET || !hasTJ) && + (_hasET || !isEncoded) && resultantText.isNotEmpty && !resultantText.endsWith(' ')) { resultantText += ' '; } - spaceBetweenWord = false; } + spaceBetweenWord = false; hasTj = true; - if (prevY != 0 && difference >= 1) { - resultantText += '\r\n'; - } currentText = currentToken == 'TJ' ? _renderTextElementTJ( elements!, token, pageResources, horizontalScaling) : _renderTextElement( elements!, token, pageResources, horizontalScaling); + if (_actualText != null && _actualText!.isNotEmpty) { + currentText = _actualText; + _actualText = null; + } _currentTextMatrix = _textLineMatrix!.clone(); prevY = currentY; resultantText += currentText!; @@ -1562,9 +1607,6 @@ class PdfTextExtractor { _textMatrix = _textLineMatrix!.clone(); if (currentToken == 'TJ') { _hasBDC = false; - hasTJ = true; - } else { - hasTJ = false; } break; } @@ -1585,7 +1627,9 @@ class PdfTextExtractor { _hasLeading = true; if (prevY != 0 && (difference >= 1 || - (i > 0 && records[i - 1].operatorName! == "'"))) { + (i > 0 && records[i - 1].operatorName! == "'")) && + resultantText.isNotEmpty && + !resultantText.endsWith('\n')) { if (isSpaceAdded && resultantText.isNotEmpty && resultantText.endsWith(' ')) { @@ -1634,6 +1678,20 @@ class PdfTextExtractor { return resultantText; } + bool _hasOctalEscape(String input) { + final RegExp octalPattern = RegExp(r'\\[0-7]{1,3}'); + return octalPattern.hasMatch(input); + } + + void _initializeActualText(String text) { + _actualText = text.substring(text.indexOf('(') + 1, text.lastIndexOf(')')); + const String bigEndianPreambleString = 'þÿ'; + if (_actualText != null && + _actualText!.startsWith(bigEndianPreambleString)) { + _actualText = null; + } + } + String _skipEscapeSequence(String text) { int index = -1; do { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart index 02cbefa09..606788483 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/text_element.dart @@ -32,7 +32,7 @@ class TextElement { late List textElementGlyphList; /// internal field - bool? isExtractTextData; + late bool isExtractTextData; /// internal field late String fontName; @@ -976,6 +976,14 @@ class TextElement { gly.width * tempFontSize, tempFontSize); textElementGlyphList.add(gly); + if (isExtractTextData && gly.toUnicode.length != 1) { + for (int i = 0; i < gly.toUnicode.length - 1; i++) { + final Glyph emptyGlyph = Glyph(); + emptyGlyph.boundingRect = + Rect.fromLTWH(gly.boundingRect.right, gly.boundingRect.top, 0, 0); + textElementGlyphList.add(emptyGlyph); + } + } _updateTextMatrix(gly); transformations._popTransform(); g.transformMatrix = defaultTransformations; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart index b4bd142e7..8649bf276 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_check_box_field.dart @@ -132,11 +132,14 @@ class PdfCheckBoxField extends PdfCheckFieldBase { String? val; if (_helper.isLoadedField) { val = _helper._enableCheckBox(value); - _helper._enableItems(value, val); + val = _helper._enableItems(value, val); } if (_checked) { _helper.dictionary!.setName(PdfName(PdfDictionaryProperties.v), val ?? PdfDictionaryProperties.yes); + _helper.dictionary!.setProperty( + PdfDictionaryProperties.usageApplication, + PdfName(val ?? PdfDictionaryProperties.yes)); } else { _helper.dictionary!.remove(PdfDictionaryProperties.v); if (_helper.dictionary! @@ -207,9 +210,9 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { bool isChecked = false; String? val; if (dictionary!.containsKey(PdfDictionaryProperties.usageApplication)) { - final PdfName? state = PdfCrossTable.dereference( - dictionary![PdfDictionaryProperties.usageApplication]) as PdfName?; - if (state != null) { + final IPdfPrimitive? state = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.usageApplication]); + if (state != null && state is PdfName) { isChecked = state.name != PdfDictionaryProperties.off; } } @@ -219,36 +222,23 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { if (val == null || val == '') { val = PdfDictionaryProperties.yes; } - dictionary!.setProperty( - PdfDictionaryProperties.usageApplication, PdfName(val)); changed = true; } } return val; } - void _enableItems(bool check, String? value) { + String? _enableItems(bool check, String? value) { if (checkBoxField.items != null && checkBoxField.items!.count > 0) { + (checkBoxField.items![defaultIndex] as PdfCheckBoxItem).checked = check; final PdfDictionary? dic = PdfFieldItemHelper.getHelper(checkBoxField.items![defaultIndex]) .dictionary; if (dic != null) { - if (value == null || value.isEmpty) { - value = getItemValue(dic, crossTable); - } - if (value == null || value.isEmpty) { - value = PdfDictionaryProperties.yes; - } - if (check) { - dic.setProperty( - PdfDictionaryProperties.usageApplication, PdfName(value)); - dic.setProperty(PdfDictionaryProperties.v, PdfName(value)); - } else { - dic.setName(PdfName(PdfDictionaryProperties.usageApplication), - PdfDictionaryProperties.off); - } + value = getItemValue(dic, crossTable); } } + return value; } /// internal method @@ -303,7 +293,7 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { @override void draw() { super.draw(); - final PdfCheckFieldState state = checkBoxField.isChecked + PdfCheckFieldState state = checkBoxField.isChecked ? PdfCheckFieldState.checked : PdfCheckFieldState.unchecked; if (!isLoadedField) { @@ -337,6 +327,9 @@ class PdfCheckBoxFieldHelper extends PdfCheckFieldBaseHelper { for (int i = 0; i < kids!.count; ++i) { final PdfCheckBoxItem item = checkBoxField._items![i] as PdfCheckBoxItem; + state = item.checked + ? PdfCheckFieldState.checked + : PdfCheckFieldState.unchecked; if (item.page != null) { drawStateItem(item.page!.graphics, state, null, item); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart index bcc7ce341..f37db6ef0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field.dart @@ -4,6 +4,7 @@ import 'package:xml/xml.dart'; import '../../interfaces/pdf_interface.dart'; import '../annotations/enum.dart'; +import '../annotations/json_parser.dart'; import '../annotations/pdf_annotation.dart'; import '../annotations/pdf_annotation_border.dart'; import '../annotations/pdf_annotation_collection.dart'; @@ -1206,13 +1207,14 @@ class PdfFieldHelper { field as PdfRadioButtonListField; for (int i = 0; i < radioButtonListField.items.count; i++) { final PdfRadioButtonListItem item = radioButtonListField.items[i]; - if (PdfFieldHelper.getHelper(item).font != null) + if (PdfFieldHelper.getHelper(item).font != null) { PdfFormHelper.getHelper(field.form!).resources.add( PdfFieldHelper.getHelper(radioButtonListField.items[i]).font, PdfName(WidgetAnnotationHelper.getHelper( PdfFieldHelper.getHelper(item).widget!) .pdfDefaultAppearance! .fontName)); + } } } } else { @@ -1317,6 +1319,15 @@ class PdfFieldHelper { IPdfPrimitive? fontDictionary = crossTable!.getObject( PdfFormHelper.getHelper(field.form!) .resources[PdfDictionaryProperties.font]); + if (height == 0) { + if (font is PdfStandardFont) { + height = getFontHeight(font.fontFamily); + if (height == 0) { + height = 12; + } + PdfFontHelper.getHelper(font).setSize(height); + } + } if (fontDictionary != null && name != null && fontDictionary is PdfDictionary && @@ -1345,7 +1356,7 @@ class PdfFieldHelper { font = _updateFontEncoding(font, fontDictionary); } } else { - if (height == 0 && standardName != _getEnumName(fontFamily)) { + if (height == 0 && standardName != getEnumName(fontFamily)) { PdfDictionary? appearanceDictionary = PdfDictionary(); if (dictionary!.containsKey(PdfDictionaryProperties.ap)) { appearanceDictionary = @@ -1387,7 +1398,7 @@ class PdfFieldHelper { } } } - if (height == 0 && standardName != _getEnumName(fontFamily)) { + if (height == 0 && standardName != getEnumName(fontFamily)) { final PdfStandardFont stdf = font as PdfStandardFont; height = getFontHeight(stdf.fontFamily); font = PdfStandardFont.prototype(stdf, height); @@ -1396,7 +1407,7 @@ class PdfFieldHelper { font = PdfStandardFont(PdfFontFamily.helvetica, height, multiStyle: fontStyle); } - if (standardName != _getEnumName(fontFamily)) { + if (standardName != getEnumName(fontFamily)) { font = _updateFontEncoding(font, fontDictionary); } PdfFontHelper.getHelper(font).metrics = @@ -1411,10 +1422,12 @@ class PdfFieldHelper { font = PdfStandardFont.prototype( PdfStandardFont(PdfFontFamily.helvetica, 8), height, multiStyle: fontStyle); + font = _createFontFromFontStream( + font, fontDictionary, height, fontStyle); final IPdfPrimitive? tempName = fontDictionary[PdfDictionaryProperties.name]; if (tempName != null && tempName is PdfName) { - if (font.name != tempName.name) { + if (font is PdfStandardFont && font.name != tempName.name) { final PdfFontHelper fontHelper = PdfFontHelper.getHelper(font); final WidthTable? widthTable = fontHelper.metrics!.widthTable; fontHelper.metrics = @@ -1454,8 +1467,10 @@ class PdfFieldHelper { fontDescriptor is PdfReferenceHolder) { fontDescriptorDic = fontDescriptor.object; } - if (fontDescriptorDic != null && fontDescriptorDic is PdfDictionary) + if (fontDescriptorDic != null && + fontDescriptorDic is PdfDictionary) { fontName = fontDescriptorDic[PdfDictionaryProperties.fontName]; + } if (fontName != null && fontName is PdfName) { String fontNameStr = fontName.name!.substring(fontName.name!.indexOf('+') + 1); @@ -1486,15 +1501,6 @@ class PdfFieldHelper { final PdfFont? usedFont = _getFontByName(name, height); usedFont != null ? font = usedFont : isCorrectFont = false; } - if (height == 0) { - if (font is PdfStandardFont) { - height = getFontHeight(font.fontFamily); - if (height == 0) { - height = 12; - } - PdfFontHelper.getHelper(font).setSize(height); - } - } return { 'font': font, 'isCorrectFont': isCorrectFont, @@ -1502,6 +1508,54 @@ class PdfFieldHelper { }; } + PdfFont _createFontFromFontStream(PdfFont font, PdfDictionary fontDictionary, + double height, List fontStyle) { + if (fontDictionary.containsKey(PdfDictionaryProperties.fontDescriptor)) { + IPdfPrimitive? fontDescriptor = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.fontDescriptor]); + if (fontDescriptor == null && + fontDescriptor is PdfDictionary && + fontDictionary.containsKey(PdfDictionaryProperties.descendantFonts)) { + final IPdfPrimitive? descendFonts = PdfCrossTable.dereference( + fontDictionary[PdfDictionaryProperties.descendantFonts]); + if (descendFonts != null && descendFonts is PdfArray) { + final IPdfPrimitive? descendDict = + PdfCrossTable.dereference(descendFonts[0]); + if (descendDict != null && + descendDict is PdfDictionary && + descendDict.containsKey(PdfDictionaryProperties.fontDescriptor)) { + fontDescriptor = PdfCrossTable.dereference( + descendDict[PdfDictionaryProperties.fontDescriptor]); + } + } + } + IPdfPrimitive? fontFile; + if (fontDescriptor != null && fontDescriptor is PdfDictionary) { + if (fontDescriptor.containsKey(PdfDictionaryProperties.fontFile2)) { + fontFile = PdfCrossTable.dereference( + fontDescriptor[PdfDictionaryProperties.fontFile2]); + } + if (fontDescriptor.containsKey('FontFile3')) { + fontFile = PdfCrossTable.dereference(fontDescriptor['FontFile3']); + } + } + if (fontFile != null && fontFile is PdfStream) { + fontFile.decompress(); + fontFile.freezeChanges(fontFile); + try { + fontFile.position = 0; + if (fontFile.dataStream != null) { + font = PdfTrueTypeFont(fontFile.dataStream!, height, + multiStyle: fontStyle); + } + } catch (e) { + return font; + } + } + } + return font; + } + PdfFontMetrics _createFont( PdfDictionary fontDictionary, double? height, PdfName baseFont) { final PdfFontMetrics fontMetrics = PdfFontMetrics(); @@ -1891,12 +1945,6 @@ class PdfFieldHelper { return font; } - String _getEnumName(dynamic annotText) { - final int index = annotText.toString().indexOf('.'); - final String name = annotText.toString().substring(index + 1); - return name[0].toUpperCase() + name.substring(1); - } - /// internal method void setFittingFontSize( GraphicsProperties gp, PaintParams prms, String text) { @@ -2006,8 +2054,9 @@ class PdfFieldHelper { .encryptor .isEncrypt! && loadedDocument.security.encryptionOptions == - PdfEncryptionOptions.encryptAllContents) + PdfEncryptionOptions.encryptAllContents) { encryptedContent = true; + } } } final PdfStream pdfStream = @@ -2130,8 +2179,23 @@ class PdfFieldHelper { } else if (_containsExportValue( value, field1._fieldHelper.dictionary!)) { field1.isChecked = true; - } else + } else if (field1.items != null && field1.items!.count > 0) { + bool isChecked = false; + for (int i = 0; i < field1.items!.count; i++) { + if (_containsExportValue( + value, + PdfFieldItemHelper.getHelper(field1.items![i]) + .dictionary!)) { + (field1.items![i] as PdfCheckBoxItem).checked = true; + isChecked = true; + } + } + if (!isChecked) { + field1.isChecked = false; + } + } else { field1.isChecked = false; + } } else if (field is PdfRadioButtonListField) { (field as PdfRadioButtonListField).selectedValue = value; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart index 32e485cc1..6bbfd82f1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item.dart @@ -18,7 +18,10 @@ import '../primitives/pdf_reference.dart'; import '../primitives/pdf_reference_holder.dart'; import '../primitives/pdf_string.dart'; import 'enum.dart'; +import 'pdf_check_box_field.dart'; import 'pdf_field.dart'; +import 'pdf_field_item_collection.dart'; +import 'pdf_form.dart'; /// Represents base class for field's group items. class PdfFieldItem { @@ -241,12 +244,107 @@ class PdfFieldItemHelper { static PdfFieldItemHelper getHelper(PdfFieldItem item) { return item._helper; } + + /// internal method + bool obtainCheckedStatus() { + bool check = false; + IPdfPrimitive? state; + if (dictionary!.containsKey(PdfDictionaryProperties.usageApplication)) { + state = PdfCrossTable.dereference( + dictionary![PdfDictionaryProperties.usageApplication]); + } + if (state == null) { + final PdfFieldHelper fieldHelper = PdfFieldHelper.getHelper(field); + final IPdfPrimitive? name = PdfFieldHelper.getValue( + fieldHelper.dictionary!, + fieldHelper.crossTable, + PdfDictionaryProperties.v, + false); + if (name != null && name is PdfName) { + check = (name.name == + fieldHelper.getItemValue(dictionary!, fieldHelper.crossTable)); + } + } else if (state is PdfName) { + check = (state.name != PdfDictionaryProperties.off); + } + return check; + } + + /// internal method + void setCheckedStatus(bool check) { + final PdfFieldHelper fieldHelper = PdfFieldHelper.getHelper(field); + String? val = fieldHelper.getItemValue(dictionary!, fieldHelper.crossTable); + if (val != null) { + _uncheckOthers(val, check, fieldHelper); + } + if (check) { + if (val == null || val.isEmpty) { + val = PdfDictionaryProperties.yes; + } + fieldHelper.dictionary!.setName(PdfName(PdfDictionaryProperties.v), val); + dictionary! + .setProperty(PdfDictionaryProperties.usageApplication, PdfName(val)); + dictionary!.setProperty(PdfDictionaryProperties.v, PdfName(val)); + } else { + IPdfPrimitive? v; + if (fieldHelper.dictionary!.containsKey(PdfDictionaryProperties.v)) { + v = PdfCrossTable.dereference( + fieldHelper.dictionary![PdfDictionaryProperties.v]); + } + if (v != null && v is PdfName && val == v.name) { + fieldHelper.dictionary!.remove(PdfDictionaryProperties.v); + } + dictionary!.setProperty(PdfDictionaryProperties.usageApplication, + PdfName(PdfDictionaryProperties.off)); + } + fieldHelper.changed = true; + } + + /// internal method + void _uncheckOthers(String value, bool check, PdfFieldHelper fieldHelper) { + final PdfFieldItemCollection? items = (field as PdfCheckBoxField).items; + if (items != null && items.count > 0) { + final PdfFieldItemCollectionHelper fieldItemCollectionHelper = + PdfFieldItemCollectionHelper.getHelper(items); + if (fieldItemCollectionHelper.allowUncheck) { + fieldItemCollectionHelper.allowUncheck = false; + for (int i = 0; i < items.count; i++) { + final PdfFieldItem item = items[i]; + if (item != fieldItem && item is PdfCheckBoxItem) { + final String? val = fieldHelper.getItemValue( + PdfFieldItemHelper.getHelper(item).dictionary!, + fieldHelper.crossTable); + final bool v = val != null && val == value; + if (v && check) { + item.checked = true; + } else { + item.checked = false; + } + } + } + } + fieldItemCollectionHelper.allowUncheck = true; + } + } } /// Represents loaded check box item. class PdfCheckBoxItem extends PdfFieldItem { - PdfCheckBoxItem._(PdfField field, int index, PdfDictionary? dictionary) - : super._(field, index, dictionary); + PdfCheckBoxItem._(super.field, super.index, super.dictionary) : super._(); + + //Properties + /// Gets or sets a value indicating whether the [PdfCheckBoxItem] is checked. + bool get checked => _helper.obtainCheckedStatus(); + + set checked(bool value) { + if (!_helper.field.readOnly) { + if (value != checked) { + _helper.setCheckedStatus(value); + PdfFormHelper.getHelper(_helper.field.form!).setAppearanceDictionary = + true; + } + } + } //Implementation void _setStyle(PdfCheckBoxStyle value) { @@ -307,8 +405,7 @@ class PdfCheckBoxItemHelper { /// Represents an item in a text box field collection. class PdfTextBoxItem extends PdfFieldItem { - PdfTextBoxItem._(PdfField field, int index, PdfDictionary? dictionary) - : super._(field, index, dictionary); + PdfTextBoxItem._(super.field, super.index, super.dictionary) : super._(); } // ignore: avoid_classes_with_only_static_members diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart index fafe1d46d..34e16fa71 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_item_collection.dart @@ -41,6 +41,9 @@ class PdfFieldItemCollectionHelper extends PdfObjectCollectionHelper { /// internal field late PdfField field; + /// internal field + bool allowUncheck = true; + /// internal method static PdfFieldItemCollection load(PdfField field) { return PdfFieldItemCollection._(field); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart index bfe0dac18..d6e56b7d6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form.dart @@ -656,8 +656,8 @@ class PdfForm implements IPdfWrapper { } } if (fieldKey != null && fieldValue != null) { - table[_decodeXMLConversion(fieldKey)] = - _decodeXMLConversion(fieldValue); + table[PdfFormHelper.decodeXMLConversion(fieldKey)] = + PdfFormHelper.decodeXMLConversion(fieldValue); fieldKey = fieldValue = null; } token = reader.getNextJsonToken(); @@ -691,7 +691,7 @@ class PdfForm implements IPdfWrapper { if (helper.isLoadedField && field.canExport) { final IPdfPrimitive? name = PdfFieldHelper.getValue(helper.dictionary!, helper.crossTable, PdfDictionaryProperties.ft, true); - if (name != null && name is PdfName) + if (name != null && name is PdfName) { switch (name.name) { case 'Tx': final IPdfPrimitive? textField = PdfFieldHelper.getValue( @@ -746,6 +746,7 @@ class PdfForm implements IPdfWrapper { } break; } + } } } bytes.addAll(utf8.encode('{')); @@ -774,27 +775,6 @@ class PdfForm implements IPdfWrapper { : value; } - String _decodeXMLConversion(String value) { - String newString = value; - while (newString.contains('_x')) { - final int index = newString.indexOf('_x'); - final String tempString = newString.substring(index); - if (tempString.length >= 7 && tempString[6] == '_') { - newString = newString.replaceRange(index, index + 2, '--'); - final int? charCode = - int.tryParse(value.substring(index + 2, index + 6), radix: 16); - if (charCode != null && charCode >= 0) { - value = value.replaceRange( - index, index + 7, String.fromCharCode(charCode)); - newString = newString.replaceRange(index, index + 7, '-'); - } - } else { - break; - } - } - return value; - } - /// Imports XML Data from the given data. void _importDataXml(List bytes, bool continueImportOnError) { final String data = utf8.decode(bytes); @@ -970,9 +950,10 @@ class PdfFormHelper { if (!_isDefaultAppearance) { needAppearances = false; } - if (dictionary!.containsKey(PdfDictionaryProperties.needAppearances)) + if (dictionary!.containsKey(PdfDictionaryProperties.needAppearances)) { dictionary!.setBoolean( PdfDictionaryProperties.needAppearances, needAppearances); + } } while (i < form.fields.count) { final PdfField field = form.fields[i]; @@ -1404,6 +1385,28 @@ class PdfFormHelper { deleteAnnotation(field); } } + + /// internal method + static String decodeXMLConversion(String value) { + String newString = value; + while (newString.contains('_x')) { + final int index = newString.indexOf('_x'); + final String tempString = newString.substring(index); + if (tempString.length >= 7 && tempString[6] == '_') { + newString = newString.replaceRange(index, index + 2, '--'); + final int? charCode = + int.tryParse(value.substring(index + 2, index + 6), radix: 16); + if (charCode != null && charCode >= 0) { + value = value.replaceRange( + index, index + 7, String.fromCharCode(charCode)); + newString = newString.replaceRange(index, index + 7, '-'); + } + } else { + break; + } + } + return value; + } } class _NodeInfo { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart index 36ffbb856..77ac61723 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_form_field_collection.dart @@ -712,8 +712,9 @@ class PdfFormFieldCollectionHelper extends PdfObjectCollectionHelper { final IPdfPrimitive? kidObject = PdfCrossTable.dereference(array[i]); if (kidObject != null && kidObject is PdfDictionary && - kidObject.containsKey(PdfDictionaryProperties.p)) + kidObject.containsKey(PdfDictionaryProperties.p)) { holder = kidObject[PdfDictionaryProperties.p]; + } if (holder != null && holder is PdfReferenceHolder && holder.object == pageReferenceHolder.object) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart index 2182804f9..3a7d486f4 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field.dart @@ -405,8 +405,9 @@ class PdfListFieldHelper extends PdfFieldHelper { selectedIndexes.sort(); dictionary! .setProperty(PdfDictionaryProperties.i, PdfArray(selectedIndexes)); - } else + } else { dictionary!.remove(PdfDictionaryProperties.i); + } } if (dictionary!.containsKey(PdfDictionaryProperties.v)) { final IPdfPrimitive? primitive = diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart index 1167cea44..ce71adb51 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_text_box_field.dart @@ -448,41 +448,45 @@ class PdfTextBoxField extends PdfField { final List ch = text.split(''); if (maxLength > 0) { width = params.bounds!.width / maxLength; - } - graphics.drawRectangle(bounds: params.bounds!, pen: _helper.borderPen); - for (int i = 0; i < maxLength; i++) { - if (_helper.format!.alignment != PdfTextAlignment.right) { - if (_helper.format!.alignment == PdfTextAlignment.center && - ch.length < maxLength) { - final int startLocation = - (maxLength / 2 - (ch.length / 2).ceil()).toInt(); - newText = i >= startLocation && i < startLocation + ch.length - ? ch[i - startLocation] - : ''; + graphics.drawRectangle( + bounds: params.bounds!, pen: _helper.borderPen); + for (int i = 0; i < maxLength; i++) { + if (_helper.format!.alignment != PdfTextAlignment.right) { + if (_helper.format!.alignment == PdfTextAlignment.center && + ch.length < maxLength) { + final int startLocation = + (maxLength / 2 - (ch.length / 2).ceil()).toInt(); + newText = i >= startLocation && i < startLocation + ch.length + ? ch[i - startLocation] + : ''; + } else { + newText = ch.length > i ? ch[i] : ''; + } } else { - newText = ch.length > i ? ch[i] : ''; + newText = maxLength - ch.length <= i + ? ch[i - (maxLength - ch.length)] + : ''; + } + params.bounds = Rect.fromLTWH(params.bounds!.left, + params.bounds!.top, width, params.bounds!.height); + final PdfStringFormat format = PdfStringFormat( + alignment: PdfTextAlignment.center, + lineAlignment: _helper.format!.lineAlignment); + FieldPainter().drawTextBox(graphics, params, newText, font, format, + insertSpaces, multiline); + params.bounds = Rect.fromLTWH(params.bounds!.left + width, + params.bounds!.top, width, params.bounds!.height); + if (params.borderWidth != 0) { + graphics.drawLine( + params.borderPen!, + Offset(params.bounds!.left, params.bounds!.top), + Offset(params.bounds!.left, + params.bounds!.top + params.bounds!.height)); } - } else { - newText = maxLength - ch.length <= i - ? ch[i - (maxLength - ch.length)] - : ''; - } - params.bounds = Rect.fromLTWH(params.bounds!.left, params.bounds!.top, - width, params.bounds!.height); - final PdfStringFormat format = PdfStringFormat( - alignment: PdfTextAlignment.center, - lineAlignment: _helper.format!.lineAlignment); - FieldPainter().drawTextBox( - graphics, params, newText, font, format, insertSpaces, multiline); - params.bounds = Rect.fromLTWH(params.bounds!.left + width, - params.bounds!.top, width, params.bounds!.height); - if (params.borderWidth != 0) { - graphics.drawLine( - params.borderPen!, - Offset(params.bounds!.left, params.bounds!.top), - Offset(params.bounds!.left, - params.bounds!.top + params.bounds!.height)); } + } else { + FieldPainter().drawTextBox(graphics, params, newText, font, + _helper.format!, insertSpaces, multiline); } } else { FieldPainter().drawTextBox(graphics, params, newText, font, diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart index d5c7d3f81..1c9f028ad 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_xfdf_document.dart @@ -3,8 +3,19 @@ import 'dart:convert'; import 'package:xml/xml.dart'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/enum.dart'; +import '../annotations/json_parser.dart'; +import '../annotations/pdf_annotation.dart'; import '../io/pdf_constants.dart'; +import '../io/pdf_cross_table.dart'; +import '../pdf_document/pdf_document.dart'; import '../primitives/pdf_array.dart'; +import '../primitives/pdf_boolean.dart'; +import '../primitives/pdf_dictionary.dart'; +import '../primitives/pdf_name.dart'; +import '../primitives/pdf_number.dart'; +import '../primitives/pdf_reference_holder.dart'; +import '../primitives/pdf_stream.dart'; import '../primitives/pdf_string.dart'; /// internal class @@ -17,6 +28,10 @@ class XFdfDocument { //Fields String? _pdfFilePath; final Map _table = {}; + List? _annotationAttributes; + bool _skipBorderStyle = false; + bool _isStampAnnotation = false; + PdfDocument? _document; //Implementation /// internal method @@ -25,15 +40,20 @@ class XFdfDocument { } /// internal method - List save() { + List save([List? annotationData]) { List xmlData; final XmlBuilder builder = XmlBuilder(); builder.processing('xml', 'version="1.0" encoding="utf-8"'); builder.element(PdfDictionaryProperties.xfdf.toLowerCase(), nest: () { builder.attribute('xmlns', 'http://ns.adobe.com/xfdf/'); builder.attribute('xml:space', 'preserve'); - builder.element(PdfDictionaryProperties.fields.toLowerCase(), - nest: _writeFormData()); + if (annotationData != null && annotationData.isNotEmpty) { + builder.element(PdfDictionaryProperties.annots.toLowerCase(), + nest: annotationData); + } else { + builder.element(PdfDictionaryProperties.fields.toLowerCase(), + nest: _writeFormData()); + } builder.element('f', nest: () { // ignore: unnecessary_null_checks builder.attribute('href', _pdfFilePath!); @@ -69,4 +89,1177 @@ class XFdfDocument { }); return elements; } + + /// internal method + XmlElement? exportAnnotationData(PdfDictionary annotationDictionary, + int pageIndex, bool exportAppearance, PdfDocument document) { + _document = document; + if (!PdfDocumentHelper.isLinkAnnotation(annotationDictionary)) { + return _writeAnnotationData( + pageIndex, exportAppearance, annotationDictionary); + } + return null; + } + + XmlElement? _writeAnnotationData(int pageIndex, bool exportAppearance, + PdfDictionary annotationDictionary) { + XmlElement? element; + final String? type = _getAnnotationType(annotationDictionary); + _skipBorderStyle = false; + if (type != null && type.isNotEmpty) { + _annotationAttributes ??= []; + element = XmlElement(XmlName(type.toLowerCase())); + element.attributes + .add(XmlAttribute(XmlName('page'), pageIndex.toString())); + switch (type) { + case PdfDictionaryProperties.line: + if (annotationDictionary.containsKey(PdfDictionaryProperties.l)) { + final IPdfPrimitive? linePoints = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.l]); + if (linePoints != null && + linePoints is PdfArray && + linePoints.count == 4 && + linePoints[0] != null && + linePoints[0] is PdfNumber && + linePoints[1] != null && + linePoints[1] is PdfNumber && + linePoints[2] != null && + linePoints[2] is PdfNumber && + linePoints[3] != null && + linePoints[3] is PdfNumber) { + element.attributes.add(XmlAttribute(XmlName('start'), + '${(linePoints[0]! as PdfNumber).value},${(linePoints[1]! as PdfNumber).value}')); + element.attributes.add(XmlAttribute(XmlName('end'), + '${(linePoints[2]! as PdfNumber).value},${(linePoints[3]! as PdfNumber).value}')); + } + } + break; + case 'Stamp': + exportAppearance = true; + _isStampAnnotation = true; + break; + case PdfDictionaryProperties.square: + if (!exportAppearance && + annotationDictionary.containsKey(PdfDictionaryProperties.it)) { + final IPdfPrimitive? name = + annotationDictionary[PdfDictionaryProperties.it]; + if (name != null && name is PdfName && name.name == 'SquareImage') { + exportAppearance = true; + } + } + break; + } + if (annotationDictionary.containsKey(PdfDictionaryProperties.be) && + annotationDictionary.containsKey(PdfDictionaryProperties.bs)) { + final IPdfPrimitive? borderEffect = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.be]); + if (borderEffect != null && + borderEffect is PdfDictionary && + borderEffect.containsKey(PdfDictionaryProperties.s)) { + _skipBorderStyle = true; + } + } + _writeDictionary( + annotationDictionary, pageIndex, element, exportAppearance); + _annotationAttributes!.clear(); + if (_isStampAnnotation) { + _isStampAnnotation = false; + } + } + return element; + } + + void _writeDictionary(PdfDictionary dictionary, int pageIndex, + XmlElement element, bool exportAppearance) { + bool isBSdictionary = false; + if (dictionary.containsKey(PdfDictionaryProperties.type)) { + final IPdfPrimitive? name = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.type]); + if (name != null && + name is PdfName && + name.name == PdfDictionaryProperties.border && + _skipBorderStyle) { + isBSdictionary = true; + } + } + dictionary.items!.forEach((PdfName? name, IPdfPrimitive? value) { + final String key = name!.name!; + if (!((!exportAppearance && key == PdfDictionaryProperties.ap) || + key == PdfDictionaryProperties.p || + key == PdfDictionaryProperties.parent)) { + final IPdfPrimitive? primitive = value; + if (primitive is PdfReferenceHolder) { + final IPdfPrimitive? obj = primitive.object; + if (obj != null && obj is PdfDictionary) { + switch (key) { + case PdfDictionaryProperties.bs: + _writeDictionary(obj, pageIndex, element, false); + break; + case PdfDictionaryProperties.be: + _writeDictionary(obj, pageIndex, element, false); + break; + case PdfDictionaryProperties.irt: + if (obj.containsKey('NM')) { + element.attributes.add( + XmlAttribute(XmlName('inreplyto'), _getValue(obj['NM']))); + } + break; + } + } + } else if (primitive is PdfDictionary) { + _writeDictionary(primitive, pageIndex, element, false); + } else if (primitive != null && (!isBSdictionary) || + (isBSdictionary && key != PdfDictionaryProperties.s)) { + _writeAttribute(element, key, primitive!); + } + } + }); + if (exportAppearance && + dictionary.containsKey(PdfDictionaryProperties.ap)) { + final IPdfPrimitive? appearance = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.ap]); + if (appearance != null && appearance is PdfDictionary) { + final List elements = _getAppearanceString(appearance); + if (elements.isNotEmpty) { + element.children.add(XmlElement(XmlName('appearance'), + [], [XmlText(base64.encode(elements))])); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.measure)) { + element.children.add(_exportMeasureDictionary(dictionary)); + } + if (dictionary.containsKey('Sound')) { + final IPdfPrimitive? sound = + PdfCrossTable.dereference(dictionary['Sound']); + if (sound != null && sound is PdfStream) { + if (sound.containsKey('B')) { + element.attributes + .add(XmlAttribute(XmlName('bits'), _getValue(sound['B']))); + } + if (sound.containsKey(PdfDictionaryProperties.c)) { + element.attributes.add(XmlAttribute(XmlName('channels'), + _getValue(sound[PdfDictionaryProperties.c]))); + } + if (sound.containsKey(PdfDictionaryProperties.e)) { + element.attributes.add(XmlAttribute(XmlName('encoding'), + _getValue(sound[PdfDictionaryProperties.e]))); + } + if (sound.containsKey(PdfDictionaryProperties.r)) { + element.attributes.add(XmlAttribute( + XmlName('rate'), _getValue(sound[PdfDictionaryProperties.r]))); + } + if (sound.dataStream != null && sound.dataStream!.isNotEmpty) { + final String data = PdfString.bytesToHex(sound.dataStream!); + if (!isNullOrEmpty(data)) { + element.children.add(XmlElement( + XmlName(XfdfProperties.data.toLowerCase()), [ + XmlAttribute(XmlName(XfdfProperties.mode), 'raw'), + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toLowerCase()), + 'hex'), + if (sound.containsKey(PdfDictionaryProperties.length)) + XmlAttribute( + XmlName(PdfDictionaryProperties.length.toLowerCase()), + _getValue(sound[PdfDictionaryProperties.length])), + if (sound.containsKey(PdfDictionaryProperties.filter)) + XmlAttribute(XmlName(PdfDictionaryProperties.filter), + _getValue(sound[PdfDictionaryProperties.filter])) + ], [ + XmlText(data) + ])); + } + } + } + } else if (dictionary.containsKey(PdfDictionaryProperties.fs)) { + final IPdfPrimitive? fsDictionary = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.fs]); + if (fsDictionary != null && fsDictionary is PdfDictionary) { + if (fsDictionary.containsKey(PdfDictionaryProperties.f)) { + element.attributes.add(XmlAttribute(XmlName('file'), + _getValue(fsDictionary[PdfDictionaryProperties.f]))); + } + if (fsDictionary.containsKey(PdfDictionaryProperties.ef)) { + final IPdfPrimitive? efDictionary = PdfCrossTable.dereference( + fsDictionary[PdfDictionaryProperties.ef]); + if (efDictionary != null && + efDictionary is PdfDictionary && + efDictionary.containsKey(PdfDictionaryProperties.f)) { + final IPdfPrimitive? fStream = PdfCrossTable.dereference( + efDictionary[PdfDictionaryProperties.f]); + if (fStream != null && fStream is PdfStream) { + if (fStream.containsKey(PdfDictionaryProperties.params)) { + final IPdfPrimitive? paramsDictionary = + PdfCrossTable.dereference( + fStream[PdfDictionaryProperties.params]); + if (paramsDictionary != null && + paramsDictionary is PdfDictionary) { + if (paramsDictionary + .containsKey(PdfDictionaryProperties.creationDate)) { + element.attributes.add(XmlAttribute( + XmlName('creation'), + _getValue(paramsDictionary[ + PdfDictionaryProperties.creationDate]))); + } + if (paramsDictionary + .containsKey(PdfDictionaryProperties.modificationDate)) { + element.attributes.add(XmlAttribute( + XmlName('modification'), + _getValue(paramsDictionary[ + PdfDictionaryProperties.modificationDate]))); + } + if (paramsDictionary + .containsKey(PdfDictionaryProperties.size)) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.size.toLowerCase()), + _getValue( + paramsDictionary[PdfDictionaryProperties.size]))); + } + if (paramsDictionary.containsKey('CheckSum')) { + final List checksum = + utf8.encode(_getValue(paramsDictionary['CheckSum'])); + final String hexString = PdfString.bytesToHex(checksum); + element.attributes + .add(XmlAttribute(XmlName('checksum'), hexString)); + } + } + } + final String data = PdfString.bytesToHex(fStream.dataStream!); + if (!isNullOrEmpty(data)) { + element.children.add(XmlElement( + XmlName(XfdfProperties.data.toLowerCase()), [ + XmlAttribute(XmlName(XfdfProperties.mode), + XfdfProperties.raw.toLowerCase()), + XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toLowerCase()), + XfdfProperties.hex.toLowerCase()), + if (fStream.containsKey(PdfDictionaryProperties.length)) + XmlAttribute( + XmlName(PdfDictionaryProperties.length.toLowerCase()), + _getValue(fStream[PdfDictionaryProperties.length])), + if (fStream.containsKey(PdfDictionaryProperties.filter)) + XmlAttribute(XmlName(PdfDictionaryProperties.filter), + _getValue(fStream[PdfDictionaryProperties.filter])) + ], [ + XmlText(data) + ])); + } + } + } + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.vertices)) { + final XmlElement verticesElement = + XmlElement(XmlName(PdfDictionaryProperties.vertices.toLowerCase())); + final IPdfPrimitive? vertices = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.vertices]); + if (vertices != null && vertices is PdfArray && vertices.count > 0) { + final int elementCount = vertices.count; + if (elementCount.isEven) { + String value = ''; + IPdfPrimitive? numberElement; + for (int i = 0; i < elementCount - 1; i++) { + numberElement = vertices.elements[i]; + if (numberElement != null && numberElement is PdfNumber) { + value += _getValue(numberElement) + (i % 2 != 0 ? ';' : ','); + } + } + numberElement = vertices.elements[elementCount - 1]; + if (numberElement != null && numberElement is PdfNumber) { + value += _getValue(numberElement); + } + if (!isNullOrEmpty(value)) { + verticesElement.children.add(XmlText(value)); + } + } + } + element.children.add(verticesElement); + } + if (dictionary.containsKey(PdfDictionaryProperties.popup)) { + final IPdfPrimitive? popup = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.popup]); + if (popup != null && popup is PdfDictionary) { + final XmlElement? popupElement = + _writeAnnotationData(pageIndex, exportAppearance, popup); + if (popupElement != null) { + element.children.add(popupElement); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.da)) { + final IPdfPrimitive? defaultAppearance = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.da]); + if (defaultAppearance != null && defaultAppearance is PdfString) { + if (!isNullOrEmpty(defaultAppearance.value)) { + element.children.add(XmlElement(XmlName('defaultappearance'), + [], [XmlText(defaultAppearance.value!)])); + } + } + } + if (dictionary.containsKey('DS')) { + final IPdfPrimitive? defaultStyle = + PdfCrossTable.dereference(dictionary['DS']); + if (defaultStyle != null && defaultStyle is PdfString) { + if (!isNullOrEmpty(defaultStyle.value)) { + element.children.add(XmlElement(XmlName('defaultstyle'), + [], [XmlText(defaultStyle.value!)])); + } + } + } + if (dictionary.containsKey('InkList')) { + final IPdfPrimitive? inkList = + PdfCrossTable.dereference(dictionary['InkList']); + if (inkList != null && inkList is PdfArray && inkList.count > 0) { + final XmlElement inkListElement = XmlElement(XmlName('inkList')); + for (int j = 0; j < inkList.count; j++) { + final IPdfPrimitive? list = PdfCrossTable.dereference(inkList[j]); + if (list != null && list is PdfArray) { + inkListElement.children.add(XmlElement(XmlName('gesture'), + [], [XmlText(_getValue(list))])); + } + } + element.children.add(inkListElement); + } + } + if (dictionary.containsKey('RC')) { + final IPdfPrimitive? contents = + PdfCrossTable.dereference(dictionary['RC']); + if (contents != null && contents is PdfString) { + String? value = contents.value; + if (!isNullOrEmpty(value)) { + final int index = value!.indexOf(' 0) { + value = value.substring(index); + } + element.children.add(XmlElement(XmlName('contents-richtext'), + [], [XmlText(value)])); + } + } + } + if (dictionary.containsKey(PdfDictionaryProperties.contents)) { + final IPdfPrimitive? contents = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.contents]); + if (contents != null && contents is PdfString) { + if (!isNullOrEmpty(contents.value)) { + element.children.add(XmlElement(XmlName('contents'), [], + [XmlText(contents.value!)])); + } + } + } + } + + String _getColor(IPdfPrimitive primitive) { + String color = ''; + final PdfArray colorArray = primitive as PdfArray; + if (colorArray.count >= 3) { + final String r = PdfString.bytesToHex([ + ((colorArray.elements[0]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + final String g = PdfString.bytesToHex([ + ((colorArray.elements[1]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + final String b = PdfString.bytesToHex([ + ((colorArray.elements[2]! as PdfNumber).value! * 255).round() + ]).toUpperCase(); + color = '#$r$g$b'; + } + return color; + } + + void _writeAttribute( + XmlElement element, String key, IPdfPrimitive primitive) { + if (_annotationAttributes != null && + !_annotationAttributes!.contains(key)) { + switch (key) { + case PdfDictionaryProperties.c: + final String color = _getColor(primitive); + if (primitive is PdfNumber) { + final String c = _getValue(primitive); + if (!isNullOrEmpty(c) && !_annotationAttributes!.contains('c')) { + element.attributes.add(XmlAttribute(XmlName('c'), c)); + _annotationAttributes!.add('c'); + } + } + if (!isNullOrEmpty(color) && + !_annotationAttributes!.contains('color')) { + element.attributes.add(XmlAttribute(XmlName('color'), color)); + _annotationAttributes!.add('color'); + } + break; + case PdfDictionaryProperties.ic: + final String interiorColor = _getColor(primitive); + if (!isNullOrEmpty(interiorColor) && + !_annotationAttributes!.contains('interior-color')) { + element.attributes + .add(XmlAttribute(XmlName('interior-color'), interiorColor)); + _annotationAttributes!.add('interior-color'); + } + break; + case PdfDictionaryProperties.m: + if (!_annotationAttributes!.contains('date')) { + element.attributes + .add(XmlAttribute(XmlName('date'), _getValue(primitive))); + _annotationAttributes!.add('date'); + } + break; + case 'NM': + if (!_annotationAttributes! + .contains(PdfDictionaryProperties.name.toLowerCase())) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.name.toLowerCase()), + _getValue(primitive))); + _annotationAttributes! + .add(PdfDictionaryProperties.name.toLowerCase()); + } + break; + case PdfDictionaryProperties.name: + if (!_annotationAttributes!.contains('icon')) { + element.attributes + .add(XmlAttribute(XmlName('icon'), _getValue(primitive))); + _annotationAttributes!.add('icon'); + } + break; + case PdfDictionaryProperties.subj: + if (!_annotationAttributes! + .contains(PdfDictionaryProperties.subject.toLowerCase())) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.subject.toLowerCase()), + _getValue(primitive))); + _annotationAttributes! + .add(PdfDictionaryProperties.subject.toLowerCase()); + } + break; + case PdfDictionaryProperties.t: + if (!_annotationAttributes! + .contains(PdfDictionaryProperties.title.toLowerCase())) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.title.toLowerCase()), + _getValue(primitive))); + _annotationAttributes! + .add(PdfDictionaryProperties.title.toLowerCase()); + } + break; + case PdfDictionaryProperties.rect: + case PdfDictionaryProperties.creationDate: + if (!_annotationAttributes!.contains(key.toLowerCase())) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive))); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.rotate: + if (!_annotationAttributes!.contains('rotation')) { + element.attributes + .add(XmlAttribute(XmlName('rotation'), _getValue(primitive))); + _annotationAttributes!.add('rotation'); + } + break; + case PdfDictionaryProperties.w: + if (!_annotationAttributes! + .contains(PdfDictionaryProperties.width.toLowerCase())) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.width.toLowerCase()), + _getValue(primitive))); + _annotationAttributes! + .add(PdfDictionaryProperties.width.toLowerCase()); + } + break; + case PdfDictionaryProperties.le: + if (primitive is PdfArray) { + if (primitive.count == 2) { + element.attributes.add(XmlAttribute( + XmlName('head'), _getValue(primitive.elements[0]))); + element.attributes.add(XmlAttribute( + XmlName('tail'), _getValue(primitive.elements[1]))); + } + } else if (primitive is PdfName && + !_annotationAttributes!.contains('head')) { + element.attributes + .add(XmlAttribute(XmlName('head'), _getValue(primitive))); + _annotationAttributes!.add('head'); + } + break; + case PdfDictionaryProperties.s: + if (!_annotationAttributes!.contains('style')) { + switch (_getValue(primitive)) { + case PdfDictionaryProperties.d: + element.attributes.add(XmlAttribute(XmlName('style'), 'dash')); + break; + case PdfDictionaryProperties.c: + element.attributes + .add(XmlAttribute(XmlName('style'), 'cloudy')); + break; + case PdfDictionaryProperties.s: + element.attributes.add(XmlAttribute(XmlName('style'), 'solid')); + break; + case 'B': + element.attributes + .add(XmlAttribute(XmlName('style'), 'bevelled')); + break; + case PdfDictionaryProperties.i: + element.attributes.add(XmlAttribute(XmlName('style'), 'inset')); + break; + case PdfDictionaryProperties.u: + element.attributes + .add(XmlAttribute(XmlName('style'), 'underline')); + break; + } + _annotationAttributes!.add('style'); + } + break; + case PdfDictionaryProperties.d: + if (!_annotationAttributes!.contains('dashes')) { + element.attributes + .add(XmlAttribute(XmlName('dashes'), _getValue(primitive))); + _annotationAttributes!.add('dashes'); + } + break; + case PdfDictionaryProperties.i: + if (!_annotationAttributes!.contains('intensity')) { + element.attributes + .add(XmlAttribute(XmlName('intensity'), _getValue(primitive))); + _annotationAttributes!.add('intensity'); + } + break; + case PdfDictionaryProperties.rd: + if (!_annotationAttributes!.contains('fringe')) { + element.attributes + .add(XmlAttribute(XmlName('fringe'), _getValue(primitive))); + _annotationAttributes!.add('fringe'); + } + break; + case PdfDictionaryProperties.it: + if (!_annotationAttributes!.contains(key)) { + element.attributes + .add(XmlAttribute(XmlName(key), _getValue(primitive))); + _annotationAttributes!.add(key); + } + break; + case 'RT': + if (!_annotationAttributes!.contains('replyType')) { + element.attributes.add(XmlAttribute( + XmlName('replyType'), _getValue(primitive).toLowerCase())); + _annotationAttributes!.add('replyType'); + } + break; + case PdfDictionaryProperties.ll: + if (!_annotationAttributes!.contains('leaderLength')) { + element.attributes.add( + XmlAttribute(XmlName('leaderLength'), _getValue(primitive))); + _annotationAttributes!.add('leaderLength'); + } + break; + case PdfDictionaryProperties.lle: + if (!_annotationAttributes!.contains('leaderExtend')) { + element.attributes.add( + XmlAttribute(XmlName('leaderExtend'), _getValue(primitive))); + _annotationAttributes!.add('leaderExtend'); + } + break; + case PdfDictionaryProperties.cap: + if (!_annotationAttributes!.contains('caption')) { + element.attributes + .add(XmlAttribute(XmlName('caption'), _getValue(primitive))); + _annotationAttributes!.add('caption'); + } + break; + case PdfDictionaryProperties.q: + if (!_annotationAttributes!.contains('justification')) { + element.attributes.add( + XmlAttribute(XmlName('justification'), _getValue(primitive))); + _annotationAttributes!.add('justification'); + } + break; + case PdfDictionaryProperties.cp: + if (!_annotationAttributes!.contains('caption-style')) { + element.attributes.add( + XmlAttribute(XmlName('caption-style'), _getValue(primitive))); + _annotationAttributes!.add('caption-style'); + } + break; + case 'CL': + if (!_annotationAttributes!.contains('callout')) { + element.attributes + .add(XmlAttribute(XmlName('callout'), _getValue(primitive))); + _annotationAttributes!.add('callout'); + } + break; + case 'FD': + if (!_annotationAttributes!.contains(key.toLowerCase())) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive))); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.quadPoints: + if (!_annotationAttributes!.contains('Coords')) { + element.attributes + .add(XmlAttribute(XmlName('coords'), _getValue(primitive))); + _annotationAttributes!.add('coords'); + } + break; + case PdfDictionaryProperties.ca: + if (!_annotationAttributes!.contains('opacity')) { + element.attributes + .add(XmlAttribute(XmlName('opacity'), _getValue(primitive))); + _annotationAttributes!.add('opacity'); + } + break; + case PdfDictionaryProperties.f: + if (primitive is PdfNumber && + !_annotationAttributes! + .contains(PdfDictionaryProperties.flags.toLowerCase())) { + final List annotationFlags = + PdfAnnotationHelper.obtainAnnotationFlags( + primitive.value!.toInt()); + final String flag = annotationFlags + .map((PdfAnnotationFlags flag) => + getEnumName(flag).toLowerCase()) + .toString() + .replaceAll(' ', ''); + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.flags.toLowerCase()), + flag.substring(1, flag.length - 1))); + _annotationAttributes! + .add(PdfDictionaryProperties.flags.toLowerCase()); + } + break; + case 'InkList': + case PdfDictionaryProperties.type: + case PdfDictionaryProperties.subtype: + case PdfDictionaryProperties.p: + case PdfDictionaryProperties.parent: + case PdfDictionaryProperties.l: + case PdfDictionaryProperties.contents: + case 'RC': + case PdfDictionaryProperties.da: + case 'DS': + case PdfDictionaryProperties.fs: + case 'MeasurementTypes': + case PdfDictionaryProperties.vertices: + case 'GroupNesting': + case 'ITEx': + break; + case PdfDictionaryProperties.open: + case 'State': + case 'StateModel': + case 'OverlayText': + case 'OC': + case PdfDictionaryProperties.llo: + case 'Repeat': + if (!_annotationAttributes!.contains(key)) { + element.attributes.add( + XmlAttribute(XmlName(key.toLowerCase()), _getValue(primitive))); + _annotationAttributes!.add(key.toLowerCase()); + } + break; + case PdfDictionaryProperties.border: + if (!_annotationAttributes! + .contains(PdfDictionaryProperties.width.toLowerCase()) && + !_annotationAttributes! + .contains(PdfDictionaryProperties.border.toLowerCase()) && + primitive is PdfArray && + primitive.count >= 3 && + primitive.elements[2] is PdfNumber) { + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.width.toLowerCase()), + _getValue(primitive.elements[2]))); + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.border.toLowerCase()), + _getValue(primitive))); + _annotationAttributes!.add(PdfDictionaryProperties.border); + _annotationAttributes!.add(PdfDictionaryProperties.width); + } + break; + default: + if (!_annotationAttributes!.contains(key)) { + element.attributes + .add(XmlAttribute(XmlName(key), _getValue(primitive))); + _annotationAttributes!.add(key); + } + break; + } + } + } + + /// internal method + String _getValue(IPdfPrimitive? primitive) { + String value = ''; + if (primitive != null) { + if (primitive is PdfName) { + value = primitive.name!; + } else if (primitive is PdfBoolean) { + value = primitive.value! ? 'yes' : 'no'; + } else if (primitive is PdfString) { + value = primitive.value!; + } else if (primitive is PdfArray) { + if (primitive.elements.isNotEmpty) { + value = _getValue(primitive.elements[0]); + } + for (int i = 1; i < primitive.elements.length; i++) { + value += ',${_getValue(primitive.elements[i])}'; + } + } else if (primitive is PdfNumber) { + value = primitive.value.toString(); + } + if (value.contains('\u0002')) { + value = value.replaceAll('\u0002', '\u2010'); + } + } + return value; + } + + String? _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? subtype = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype]); + if (subtype != null && subtype is PdfName && subtype.name != null) { + return subtype.name!; + } + } + return null; + } + + List _getAppearanceString(PdfDictionary appearanceDictionary) { + final XmlBuilder builder = XmlBuilder(); + builder.processing('xml', 'version="1.0" encoding="utf-8"'); + builder.element('DICT', + attributes: { + XfdfProperties.key: PdfDictionaryProperties.ap + }, + nest: _writeAppearanceDictionary(appearanceDictionary)); + final String xmlString = builder.buildDocument().toXmlString(pretty: true); + return utf8.encode(xmlString); + } + + List _writeAppearanceDictionary(PdfDictionary dictionary) { + final List elements = []; + if (dictionary.count > 0) { + dictionary.items!.forEach((PdfName? key, IPdfPrimitive? value) { + final XmlElement? element = _writeObject(key!.name!, value); + if (element != null) { + elements.add(element); + } + }); + } + return elements; + } + + XmlElement? _writeObject(String key, IPdfPrimitive? primitive) { + XmlElement? element; + if (primitive != null) { + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfReferenceHolder': + element = _writeObject(key, (primitive as PdfReferenceHolder).object); + break; + case 'PdfDictionary': + element = XmlElement(XmlName(XfdfProperties.dict)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.children + .addAll(_writeAppearanceDictionary(primitive as PdfDictionary)); + break; + case 'PdfStream': + final PdfStream streamElement = primitive as PdfStream; + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + element = XmlElement(XmlName(XfdfProperties.stream)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.define), '')); + element.children.addAll(_writeAppearanceDictionary(streamElement)); + final String type = + _getValue(streamElement[PdfDictionaryProperties.subtype]); + final XmlElement dataElement = + XmlElement(XmlName(XfdfProperties.data)); + if ((streamElement.containsKey(PdfDictionaryProperties.subtype) && + PdfDictionaryProperties.image == type) || + (!streamElement.containsKey(PdfDictionaryProperties.type) && + !streamElement + .containsKey(PdfDictionaryProperties.subtype))) { + dataElement.attributes.add(XmlAttribute( + XmlName(XfdfProperties.mode), XfdfProperties.raw)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex)); + String data = ''; + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else if (streamElement + .containsKey(PdfDictionaryProperties.subtype) && + PdfDictionaryProperties.form == type && + !_isStampAnnotation) { + dataElement.attributes.add(XmlAttribute( + XmlName(XfdfProperties.mode), XfdfProperties.raw)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex)); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else { + streamElement.decompress(); + if (streamElement.dataStream != null) { + final String ascii = utf8.decode(streamElement.dataStream!); + if (_isStampAnnotation && !ascii.contains('TJ')) { + dataElement.attributes.add(XmlAttribute( + XmlName(XfdfProperties.mode), XfdfProperties.filtered)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.ascii)); + if (!isNullOrEmpty(ascii)) { + dataElement.children + .add(XmlText(_getFormatedString(ascii))); + } + } + } else { + dataElement.attributes.add(XmlAttribute( + XmlName(XfdfProperties.mode), XfdfProperties.raw)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex)); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } + } + element.children.add(dataElement); + } + break; + case 'PdfBoolean': + element = XmlElement(XmlName(XfdfProperties.bool)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.attributes.add(XmlAttribute( + XmlName(XfdfProperties.val), + primitive is PdfBoolean && + primitive.value != null && + primitive.value! + ? 'true' + : 'false')); + break; + case 'PdfName': + element = XmlElement(XmlName(XfdfProperties.name)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.attributes.add(XmlAttribute( + XmlName(XfdfProperties.val), (primitive as PdfName).name ?? '')); + break; + case 'PdfString': + element = XmlElement(XmlName(XfdfProperties.string)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.attributes.add(XmlAttribute(XmlName(XfdfProperties.val), + (primitive as PdfString).value ?? '')); + break; + case 'PdfNumber': + element = XmlElement(XmlName(XfdfProperties.fixed)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + final String value = + (primitive as PdfNumber).value!.toDouble().toStringAsFixed(6); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.val), value)); + break; + case 'PdfNull': + element = XmlElement(XmlName(XfdfProperties.nullVal)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + break; + case 'PdfArray': + element = XmlElement(XmlName(XfdfProperties.array)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.key), key)); + element.children.addAll(_writeArray(primitive as PdfArray)); + break; + } + } + return element; + } + + List _writeArray(PdfArray array) { + final List elements = []; + for (final IPdfPrimitive? element in array.elements) { + final XmlElement? xmlElement = _writeArrayElement(element!); + if (xmlElement != null) { + elements.add(xmlElement); + } + } + return elements; + } + + XmlElement? _writeArrayElement(IPdfPrimitive primitive) { + XmlElement? element; + final String type = primitive.runtimeType.toString(); + switch (type) { + case 'PdfArray': + element = XmlElement(XmlName(XfdfProperties.array)); + element.children.addAll(_writeArray(primitive as PdfArray)); + break; + case 'PdfName': + element = XmlElement(XmlName(XfdfProperties.name)); + element.attributes.add(XmlAttribute( + XmlName(XfdfProperties.val), (primitive as PdfName).name ?? '')); + break; + case 'PdfString': + element = XmlElement(XmlName(XfdfProperties.string)); + final RegExp regex = RegExp(r'[\u0085-\u00FF]'); + if ((primitive as PdfString).value != null && + regex.hasMatch(primitive.value!) && + primitive.isHex != null && + primitive.isHex!) { + final List bytes = primitive.pdfEncode(_document); + primitive.value = PdfString.byteToString(bytes); + element.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex)); + if (!isNullOrEmpty(primitive.value)) { + element.children.add(XmlText(_getFormatedString(primitive.value!))); + } + } else { + element.attributes.add( + XmlAttribute(XmlName(XfdfProperties.val), primitive.value ?? '')); + } + break; + case 'PdfNumber': + element = XmlElement(XmlName(XfdfProperties.fixed)); + final String value = + (primitive as PdfNumber).value!.toDouble().toStringAsFixed(6); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.val), value)); + break; + case 'PdfBoolean': + element = XmlElement(XmlName(XfdfProperties.bool)); + element.attributes.add(XmlAttribute( + XmlName(XfdfProperties.val), + (primitive as PdfBoolean).value != null && primitive.value! + ? 'true' + : 'false')); + break; + case 'PdfDictionary': + element = XmlElement(XmlName(XfdfProperties.dict)); + element.children + .addAll(_writeAppearanceDictionary(primitive as PdfDictionary)); + break; + case 'PdfStream': + final PdfStream streamElement = primitive as PdfStream; + if (streamElement.dataStream != null && + streamElement.dataStream!.isNotEmpty) { + element = XmlElement(XmlName(XfdfProperties.stream)); + element.attributes + .add(XmlAttribute(XmlName(XfdfProperties.define), '')); + element.children.addAll(_writeAppearanceDictionary(streamElement)); + final XmlElement dataElement = + XmlElement(XmlName(XfdfProperties.data)); + final String type = + _getValue(streamElement[PdfDictionaryProperties.subtype]); + if (streamElement.containsKey(PdfDictionaryProperties.subtype) && + PdfDictionaryProperties.image == type) { + dataElement.attributes.add( + XmlAttribute(XmlName(XfdfProperties.mode), XfdfProperties.raw)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.hex)); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = PdfString.bytesToHex(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(data)); + } + } else { + dataElement.attributes.add(XmlAttribute( + XmlName(XfdfProperties.mode), XfdfProperties.filtered)); + dataElement.attributes.add(XmlAttribute( + XmlName(PdfDictionaryProperties.encoding.toUpperCase()), + XfdfProperties.ascii)); + String data = ''; + streamElement.decompress(); + if (streamElement.dataStream != null) { + data = utf8.decode(streamElement.dataStream!); + } + if (data.isNotEmpty) { + dataElement.children.add(XmlText(_getFormatedString(data))); + } + } + element.children.add(dataElement); + } + break; + case 'PdfReferenceHolder': + if ((primitive as PdfReferenceHolder).object != null) { + element = _writeArrayElement(primitive.object!); + } + break; + } + return element; + } + + XmlElement _exportMeasureDictionary(PdfDictionary dictionary) { + final XmlElement measureXmlElement = XmlElement(XmlName('measure')); + final IPdfPrimitive? mdictionary = + PdfCrossTable.dereference(dictionary[PdfDictionaryProperties.measure]); + if (mdictionary != null && mdictionary is PdfDictionary) { + if (mdictionary.containsKey(PdfDictionaryProperties.r)) { + measureXmlElement.attributes.add(XmlAttribute(XmlName('rateValue'), + _getValue(mdictionary[PdfDictionaryProperties.r]))); + } + if (mdictionary.containsKey(PdfDictionaryProperties.a)) { + IPdfPrimitive? aprimitive = + PdfCrossTable.dereference(mdictionary[PdfDictionaryProperties.a]); + if (aprimitive != null && aprimitive is PdfArray) { + aprimitive = PdfCrossTable.dereference(aprimitive.elements[0]); + if (aprimitive != null && aprimitive is PdfDictionary) { + final XmlElement areaXmlElement = XmlElement(XmlName('area')); + _exportMeasureFormatDetails(aprimitive, areaXmlElement); + measureXmlElement.children.add(areaXmlElement); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.d)) { + IPdfPrimitive? dprimitive = + PdfCrossTable.dereference(mdictionary[PdfDictionaryProperties.d]); + if (dprimitive != null && dprimitive is PdfArray) { + dprimitive = PdfCrossTable.dereference(dprimitive.elements[0]); + if (dprimitive != null && dprimitive is PdfDictionary) { + final XmlElement distanceXmlElement = + XmlElement(XmlName('distance')); + _exportMeasureFormatDetails(dprimitive, distanceXmlElement); + measureXmlElement.children.add(distanceXmlElement); + } + } + } + if (mdictionary.containsKey(PdfDictionaryProperties.x)) { + IPdfPrimitive? xprimitive = + PdfCrossTable.dereference(mdictionary[PdfDictionaryProperties.x]); + if (xprimitive != null && xprimitive is PdfArray) { + xprimitive = PdfCrossTable.dereference(xprimitive.elements[0]); + if (xprimitive != null && xprimitive is PdfDictionary) { + final XmlElement xformatXmlElement = XmlElement(XmlName('xformat')); + _exportMeasureFormatDetails(xprimitive, xformatXmlElement); + measureXmlElement.children.add(xformatXmlElement); + } + } + } + } + return measureXmlElement; + } + + void _exportMeasureFormatDetails( + PdfDictionary measurementDetails, XmlElement element) { + if (measurementDetails.containsKey(PdfDictionaryProperties.c)) { + element.attributes.add(XmlAttribute(XmlName('c'), + _getValue(measurementDetails[PdfDictionaryProperties.c]))); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.f)) { + element.attributes.add(XmlAttribute(XmlName('f'), + _getValue(measurementDetails[PdfDictionaryProperties.f]))); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.d)) { + element.attributes.add(XmlAttribute(XmlName('d'), + _getValue(measurementDetails[PdfDictionaryProperties.d]))); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.rd)) { + element.attributes.add(XmlAttribute(XmlName('rd'), + _getValue(measurementDetails[PdfDictionaryProperties.rd]))); + } + if (measurementDetails.containsKey(PdfDictionaryProperties.u)) { + element.attributes.add(XmlAttribute(XmlName('u'), + _getValue(measurementDetails[PdfDictionaryProperties.u]))); + } + if (measurementDetails.containsKey('RT')) { + element.attributes.add( + XmlAttribute(XmlName('rt'), _getValue(measurementDetails['RT']))); + } + if (measurementDetails.containsKey('SS')) { + element.attributes.add( + XmlAttribute(XmlName('ss'), _getValue(measurementDetails['SS']))); + } + if (measurementDetails.containsKey('FD')) { + element.attributes.add( + XmlAttribute(XmlName('fd'), _getValue(measurementDetails['FD']))); + } + } + + String _getFormatedString(String value) { + value = value.replaceAll('&', '&'); + value = value.replaceAll('<', '<'); + value = value.replaceAll('>', '>'); + return value; + } +} + +/// XFDF dictionary properties. +class XfdfProperties { + //Constants + /// internal field + static const String dict = 'DICT'; + + /// internal field + static const String key = 'KEY'; + + /// internal field + static const String stream = 'STREAM'; + + /// internal field + static const String define = 'DEFINE'; + + /// internal field + static const String data = 'DATA'; + + /// internal field + static const String mode = 'MODE'; + + /// internal field + static const String raw = 'RAW'; + + /// internal field + static const String hex = 'HEX'; + + /// internal field + static const String filtered = 'FILTERED'; + + /// internal field + static const String ascii = 'ASCII'; + + /// internal field + static const String bool = 'BOOL'; + + /// internal field + static const String val = 'VAL'; + + /// internal field + static const String name = 'NAME'; + + /// internal field + static const String string = 'STRING'; + + /// internal field + static const String int = 'INT'; + + /// internal field + static const String fixed = 'FIXED'; + + /// internal field + static const String nullVal = 'NULL'; + + /// internal field + static const String array = 'ARRAY'; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart index 4fc3eaba3..9973ef5ff 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/layout_element.dart @@ -150,7 +150,7 @@ class PdfLayoutElementHelper { class EndTextPageLayoutArgs extends EndPageLayoutArgs { /// Initializes a new instance of the [EndTextPageLayoutArgs] class /// with the specified [PdfTextLayoutResult]. - EndTextPageLayoutArgs(PdfTextLayoutResult result) : super(result); + EndTextPageLayoutArgs(PdfTextLayoutResult super.result); } /// Provides data for event once lay outing completed on the new page. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart index 12e2ed10c..9fb5915d4 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/shape_layouter.dart @@ -14,7 +14,7 @@ import 'text_layouter.dart'; class ShapeLayouter extends ElementLayouter { // constructor /// internal constructor - ShapeLayouter(PdfShapeElement element) : super(element); + ShapeLayouter(PdfShapeElement super.element); // properties /// Gets shape element. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart index ae4ecda59..d96c4cb39 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/figures/base/text_layouter.dart @@ -16,7 +16,7 @@ import 'layout_element.dart'; class TextLayouter extends ElementLayouter { //Constructor /// internal constructor - TextLayouter(PdfTextElement element) : super(element); + TextLayouter(PdfTextElement super.element); //Fields PdfStringFormat? _format; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart index 99871253b..c67936ce5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/pdf_resources.dart @@ -16,7 +16,7 @@ import 'pdf_transparency.dart'; class PdfResources extends PdfDictionary { //Constructor /// Initializes a new instance of the [PdfResources] class. - PdfResources([PdfDictionary? baseDictionary]) : super(baseDictionary); + PdfResources([super.baseDictionary]); //Fields Map? _resourceNames; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart index 5978a9c1c..5d8161f57 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/cross_table.dart @@ -27,6 +27,13 @@ class CrossTable { _initialize(); } + /// internal constructor + CrossTable.fromFdf(List docStream, PdfCrossTable crossTable) { + _data = docStream; + _crossTable = crossTable; + objects = {}; + } + //Fields late List _data; late PdfCrossTable _crossTable; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart index 5a9e8b868..c7f20b441 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_constants.dart @@ -814,6 +814,48 @@ class PdfDictionaryProperties { /// internal field static const String irt = 'IRT'; + + /// internal field + static const String b = 'B'; + + /// internal field + static const String nm = 'NM'; + + /// internal field + static const String cl = 'CL'; + + /// internal field + static const String ds = 'DS'; + + /// internal field + static const String rc = 'RC'; + + /// internal field + static const String repeat = 'Repeat'; + + /// internal field + static const String overlayText = 'OverlayText'; + + /// internal field + static const String inkList = 'InkList'; + + /// internal field + static const String customData = 'CustomData'; + + /// internal field + static const String sound = 'Sound'; + + /// internal field + static const String rt = 'RT'; + + /// internal field + static const String ss = 'SS'; + + /// internal field + static const String fd = 'FD'; + + /// internal field + static const String targetUnitConversion = 'TargetUnitConversion'; } /// Class of string PDF common operators. diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart index eb007184c..f4d5cefd1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_cross_table.dart @@ -53,6 +53,13 @@ class PdfCrossTable { _bForceNew = true; _isColorSpace = false; } + + /// internal constructor + PdfCrossTable.fromFdf(List docStream) { + _data = docStream; + crossTable = CrossTable.fromFdf(docStream, this); + } + //Fields PdfDocument? _pdfDocument; int _count = 0; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart index b9e59e3fe..84c328361 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_parser.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import '../../interfaces/pdf_interface.dart'; +import '../annotations/fdf_parser.dart'; import '../primitives/pdf_array.dart'; import '../primitives/pdf_boolean.dart'; import '../primitives/pdf_dictionary.dart'; @@ -90,6 +91,12 @@ class PdfParser { return _windows1252MapTable; } + /// internal property + PdfTokenType? get next => _next; + + /// internal property + PdfLexer? get lexer => _lexer; + //Implementation /// internal method void setOffset(int offset) { @@ -100,6 +107,41 @@ class PdfParser { _lexer!.reset(); } + /// internal method + void advance() { + if (_cTable != null && _cTable!.validateSyntax) { + _lexer!.getNextToken(); + } + _next = _lexer!.getNextToken(); + } + + /// Read fdf object + FdfObject? parseObject() { + final IPdfPrimitive? num1 = simple(); + final IPdfPrimitive? num2 = simple(); + _match(_next, PdfTokenType.objectStart); + advance(); + final IPdfPrimitive? obj = simple(); + if (_next != PdfTokenType.objectEnd) { + _next = PdfTokenType.objectEnd; + } + _match(_next, PdfTokenType.objectEnd); + return num1 != null && + num2 != null && + obj != null && + num1 is PdfNumber && + num2 is PdfNumber + ? FdfObject(num1, num2, obj) + : null; + } + + /// internal method + IPdfPrimitive trailer() { + _match(_next, PdfTokenType.trailer); + advance(); + return _dictionary(); + } + PdfNumber? _parseInteger() { final double? value = double.tryParse(_lexer!.text); PdfNumber? integer; @@ -108,7 +150,7 @@ class PdfParser { } else { _error(_ErrorType.badlyFormedInteger, _lexer!.text); } - _advance(); + advance(); return integer; } @@ -128,7 +170,7 @@ class PdfParser { final PdfReference reference = PdfReference(integer!.value!.toInt(), integer2!.value!.toInt()); obj = PdfReferenceHolder.fromReference(reference, _crossTable); - _advance(); + advance(); } else { integerQueue.addLast(integer2!.value!.toInt()); } @@ -137,7 +179,7 @@ class PdfParser { } void _parseOldXRef(CrossTable cTable, Map? objects) { - _advance(); + advance(); while (_isSubsection()) { cTable.parseSubsection(this, objects); } @@ -159,10 +201,10 @@ class PdfParser { Map parseCrossReferenceTable( Map? objects, CrossTable cTable) { IPdfPrimitive? obj; - _advance(); + advance(); if (_next == PdfTokenType.xRef) { _parseOldXRef(cTable, objects); - obj = _trailer(); + obj = trailer(); final PdfDictionary trailerDic = obj as PdfDictionary; if (trailerDic.containsKey('Size')) { final int size = (trailerDic['Size']! as PdfNumber).value!.toInt(); @@ -216,16 +258,10 @@ class PdfParser { return {'object': obj, 'objects': objects}; } - IPdfPrimitive _trailer() { - _match(_next, PdfTokenType.trailer); - _advance(); - return _dictionary(); - } - /// internal method IPdfPrimitive? parseOffset(int offset) { setOffset(offset); - _advance(); + advance(); return _parse(); } @@ -234,14 +270,14 @@ class PdfParser { simple(); simple(); _match(_next, PdfTokenType.objectStart); - _advance(); + advance(); final IPdfPrimitive? obj = simple(); if (_next != PdfTokenType.objectEnd) { _next = PdfTokenType.objectEnd; } _match(_next, PdfTokenType.objectEnd); if (!_lexer!.skip) { - _advance(); + advance(); } else { _lexer!.skip = false; } @@ -284,7 +320,7 @@ class PdfParser { break; case PdfTokenType.nullType: obj = PdfNull(); - _advance(); + advance(); break; // ignore: no_default_cases default: @@ -304,7 +340,7 @@ class PdfParser { } else { _error(_ErrorType.badlyFormedReal, _lexer!.text); } - _advance(); + advance(); return real; } @@ -312,7 +348,7 @@ class PdfParser { _match(_next, PdfTokenType.boolean); final bool value = _lexer!.text == 'true'; final PdfBoolean result = PdfBoolean(value); - _advance(); + advance(); return result; } @@ -320,14 +356,14 @@ class PdfParser { _match(_next, PdfTokenType.name); final String name = _lexer!.text.substring(1); final PdfName result = PdfName(name); - _advance(); + advance(); return result; } /// internal method void startFrom(int offset) { setOffset(offset); - _advance(); + advance(); } /// internal method @@ -423,7 +459,7 @@ class PdfParser { } else { str.encode = ForceEncoding.unicode; } - _advance(); + advance(); return str; } @@ -452,7 +488,7 @@ class PdfParser { str.encode = ForceEncoding.unicode; } if (!_lexer!.skip) { - _advance(); + advance(); } else { _next = PdfTokenType.dictionaryEnd; } @@ -557,7 +593,7 @@ class PdfParser { IPdfPrimitive _hexString() { _match(_next, PdfTokenType.hexStringStart); - _advance(); + advance(); String sb = ''; bool isHex = true; while (_next != PdfTokenType.hexStringEnd) { @@ -569,10 +605,10 @@ class PdfParser { text = text.substring(1); } sb += text; - _advance(); + advance(); } _match(_next, PdfTokenType.hexStringEnd); - _advance(); + advance(); final PdfString result = PdfString(sb, !isHex); if (_isColorSpace) { result.isColorSpace = true; @@ -582,7 +618,7 @@ class PdfParser { IPdfPrimitive _array() { _match(_next, PdfTokenType.arrayStart); - _advance(); + advance(); IPdfPrimitive? obj; final PdfArray array = PdfArray(); _lexer!.isArray = true; @@ -594,11 +630,11 @@ class PdfParser { _isColorSpace = false; } if (_next == PdfTokenType.unknown) { - _advance(); + advance(); } } _match(_next, PdfTokenType.arrayEnd); - _advance(); + advance(); _lexer!.isArray = false; array.freezeChanges(this); return array; @@ -606,7 +642,7 @@ class PdfParser { IPdfPrimitive _dictionary() { _match(_next, PdfTokenType.dictionaryStart); - _advance(); + advance(); final PdfDictionary dic = PdfDictionary(); _Pair pair = _readPair(); while (pair.name != null && pair._value != null) { @@ -620,7 +656,7 @@ class PdfParser { } _match(_next, PdfTokenType.dictionaryEnd); if (!_lexer!.skip) { - _advance(); + advance(); } else { _next = PdfTokenType.objectEnd; _lexer!.skip = false; @@ -663,12 +699,12 @@ class PdfParser { _reader.position = position; final List buffer = _lexer!.readBytes(streamLength); final PdfStream innerStream = PdfStream(dic, buffer); - _advance(); + advance(); if (_next != PdfTokenType.streamEnd) { _next = PdfTokenType.streamEnd; } _match(_next, PdfTokenType.streamEnd); - _advance(); + advance(); if (_next != PdfTokenType.objectEnd) {} return innerStream; } else if (reference != null) { @@ -702,12 +738,12 @@ class PdfParser { final List buf = _lexer!.readBytes(streamLength); stream = PdfStream(dic, buf); } - _advance(); + advance(); if (_next != PdfTokenType.streamEnd) { _next = PdfTokenType.streamEnd; } _match(_next, PdfTokenType.streamEnd); - _advance(); + advance(); if (_next != PdfTokenType.objectEnd) { _next = PdfTokenType.objectEnd; } @@ -718,7 +754,7 @@ class PdfParser { String getObjectFlag() { _match(_next, PdfTokenType.objectType); final String type = _lexer!.text[0]; - _advance(); + advance(); return type; } @@ -769,9 +805,9 @@ class PdfParser { /// internal method int startCrossReference() { - _advance(); + advance(); _match(_next, PdfTokenType.startXRef); - _advance(); + advance(); final PdfNumber? number = _number() as PdfNumber?; if (number != null) { return number.value!.toInt(); @@ -822,13 +858,6 @@ class PdfParser { throw ArgumentError.value(error, message); } - - void _advance() { - if (_cTable != null && _cTable!.validateSyntax) { - _lexer!.getNextToken(); - } - _next = _lexer!.getNextToken(); - } } enum _ErrorType { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart index dd0d00cf0..66360efd6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page.dart @@ -162,7 +162,7 @@ class PdfPage implements IPdfWrapper { _helper.dictionary![PdfDictionaryProperties.annots] as PdfArray?; } } else { - if (_helper._annotations == null) { + if (_helper._annotations == null || _helper.importAnnotation) { // Create the annotations. _helper.createAnnotations(_helper.getWidgetReferences()); } @@ -472,6 +472,9 @@ class PdfPageHelper { /// internal field late PdfPage base; + /// internal field + bool importAnnotation = false; + /// internal method static PdfPageHelper getHelper(PdfPage base) { return base._helper; @@ -782,13 +785,15 @@ class PdfPageHelper { if (annots != null) { for (int count = 0; count < annots.count; ++count) { PdfDictionary? annotDictionary; - if (crossTable!.getObject(annots[count]) is PdfDictionary) + if (crossTable!.getObject(annots[count]) is PdfDictionary) { annotDictionary = crossTable!.getObject(annots[count]) as PdfDictionary?; + } PdfReferenceHolder? annotReference; - if (crossTable!.getObject(annots[count]) is PdfReferenceHolder) + if (crossTable!.getObject(annots[count]) is PdfReferenceHolder) { annotReference = crossTable!.getObject(annots[count]) as PdfReferenceHolder?; + } if (document != null && PdfDocumentHelper.getHelper(document!).crossTable.encryptor != null && @@ -974,6 +979,9 @@ class PdfPageHelper { } } } + if (importAnnotation) { + importAnnotation = false; + } _annotations = PdfAnnotationCollectionHelper.load(base); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart index 578176ba9..cc4751f04 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/attachments/pdf_attachment.dart @@ -12,9 +12,8 @@ class PdfAttachment extends PdfEmbeddedFileSpecification { //Constructor. /// Initializes a new instance of the [PdfAttachment] class with specified /// file name and byte data to be attached. - PdfAttachment(String fileName, List data, - {String? description, String? mimeType}) - : super(fileName, data) { + PdfAttachment(super.fileName, super.data, + {String? description, String? mimeType}) { _embeddedFile = PdfEmbeddedFileSpecificationHelper.getHelper(this).embeddedFile; _updateValues(description, mimeType); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart index fb33ba14f..933315bac 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.dart @@ -1,4 +1,3 @@ -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_automatic_field.dart'; @@ -79,11 +78,10 @@ class PdfCompositeField extends PdfMultipleValueField { /// document.dispose(); /// ``` PdfCompositeField( - {PdfFont? font, - PdfBrush? brush, + {super.font, + super.brush, String? text, - List? fields}) - : super(font: font, brush: brush) { + List? fields}) { this.text = (text == null) ? '' : text; if (fields != null) { this.fields = fields; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart index dcfe61637..139999c08 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_date_time_field.dart @@ -1,9 +1,6 @@ -import 'dart:ui'; - import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_static_field.dart'; @@ -86,8 +83,7 @@ class PdfDateTimeField extends PdfStaticField { /// //Dispose the document. /// document.dispose(); /// ``` - PdfDateTimeField({PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, brush: brush, bounds: bounds); + PdfDateTimeField({super.font, super.brush, super.bounds}); /// Get the current date and set the required date. /// ```dart diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart index 3834c3a91..1e691b452 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_destination_page_number_field.dart @@ -12,8 +12,7 @@ class PdfDestinationPageNumberField extends PdfPageNumberField { /// Initializes a new instance of the [PdfDestinationPageNumberField] class /// may include with [PdfFont], [PdfBrush] and [Rect]. PdfDestinationPageNumberField( - {PdfPage? page, PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, brush: brush, bounds: bounds) { + {PdfPage? page, super.font, super.brush, super.bounds}) { if (page != null) { this.page = page; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart index f41c77381..897c668de 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart @@ -1,9 +1,7 @@ import 'dart:ui'; import '../../drawing/drawing.dart'; -import '../../graphics/brushes/pdf_solid_brush.dart'; import '../../graphics/figures/pdf_template.dart'; -import '../../graphics/fonts/pdf_font.dart'; import '../../graphics/pdf_graphics.dart'; import 'pdf_automatic_field.dart'; import 'pdf_dynamic_field.dart'; @@ -13,8 +11,7 @@ import 'pdf_template_value_pair.dart'; abstract class PdfMultipleValueField extends PdfDynamicField { // constructor /// internal constructor - PdfMultipleValueField({PdfFont? font, PdfBrush? brush, Rect? bounds}) - : super(font: font, bounds: bounds, brush: brush); + PdfMultipleValueField({super.font, super.brush, super.bounds}); // fields final Map _list = diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart index 24ede72fe..c4bb78108 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart @@ -93,8 +93,7 @@ class PdfPageNumberField extends PdfMultipleValueField { /// document.dispose(); /// ``` PdfPageNumberField( - {PdfFont? font, PdfBrush? brush, Rect? bounds, bool? isSectionPageNumber}) - : super(font: font, brush: brush, bounds: bounds) { + {super.font, super.brush, super.bounds, bool? isSectionPageNumber}) { _helper = PdfPageNumberFieldHelper(this); _helper._isSectionPageNumber = isSectionPageNumber != null && isSectionPageNumber; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart index c6f1b10dd..c240a07c9 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/outlines/pdf_outline.dart @@ -73,8 +73,8 @@ class PdfBookmark extends PdfBookmarkBase { } } - PdfBookmark._load(PdfDictionary? dictionary, PdfCrossTable crossTable) - : super._load(dictionary, crossTable); + PdfBookmark._load(super.dictionary, PdfCrossTable super.crossTable) + : super._load(); //Fields /// Internal variable to store destination. @@ -841,87 +841,90 @@ class PdfBookmark extends PdfBookmarkBase { } if (array != null) { - final PdfReferenceHolder? holder = array[0] as PdfReferenceHolder?; + final IPdfPrimitive? holder = array[0]; PdfPage? page; - if (holder != null) { - final PdfDictionary? dic = - _helper._crossTable.getObject(holder) as PdfDictionary?; - if (ldDoc != null && dic != null) { + if (holder != null && holder is PdfReferenceHolder) { + final IPdfPrimitive? dic = _helper._crossTable.getObject(holder); + if (ldDoc != null && dic != null && dic is PdfDictionary) { page = PdfPageCollectionHelper.getHelper(ldDoc.pages).getPage(dic); } - PdfName? mode; + IPdfPrimitive? mode; if (array.count > 1) { - mode = array[1]! as PdfName; + mode = array[1]; } - if (mode != null) { + if (mode != null && mode is PdfName) { if (mode.name == PdfDictionaryProperties.xyz) { - PdfNumber? left; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - top = array[3]! as PdfNumber; + top = array[3]; } - PdfNumber? zoom; + IPdfPrimitive? zoom; if (array.count > 4) { - zoom = array[4]! as PdfNumber; + zoom = array[4]; } - if (page != null) { - final double topValue = - (top == null) ? 0 : page.size.height - top.value!; - final double leftValue = - (left == null) ? 0 : left.value! as double; + final double topValue = (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; + final double leftValue = (left != null && left is PdfNumber) + ? left.value! as double + : 0; _destination = PdfDestination(page, Offset(leftValue, topValue)); - if (zoom != null) { - _destination!.zoom = zoom.value!.toDouble(); - } + _destination!.zoom = (zoom != null && zoom is PdfNumber) + ? zoom.value!.toDouble() + : 0; } } else { if (mode.name == PdfDictionaryProperties.fitR) { - PdfNumber? left; - PdfNumber? bottom; - PdfNumber? right; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? bottom; + IPdfPrimitive? right; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - bottom = array[3]! as PdfNumber; + bottom = array[3]; } if (array.count > 4) { - right = array[4]! as PdfNumber; + right = array[4]; } if (array.count > 5) { - top = array[5]! as PdfNumber; + top = array[5]; } - if (page != null) { - left = (left == null) ? PdfNumber(0) : left; - bottom = (bottom == null) ? PdfNumber(0) : bottom; - right = (right == null) ? PdfNumber(0) : right; - top = (top == null) ? PdfNumber(0) : top; - _destination = PdfDestinationHelper.getDestination( page, PdfRectangle( - left.value!.toDouble(), - bottom.value!.toDouble(), - right.value!.toDouble(), - top.value!.toDouble())); + (left != null && left is PdfNumber) + ? left.value!.toDouble() + : 0, + (bottom != null && bottom is PdfNumber) + ? bottom.value!.toDouble() + : 0, + (right != null && right is PdfNumber) + ? right.value!.toDouble() + : 0, + (top != null && top is PdfNumber) + ? top.value!.toDouble() + : 0)); _destination!.mode = PdfDestinationMode.fitR; } } else if (mode.name == PdfDictionaryProperties.fitBH || mode.name == PdfDictionaryProperties.fitH) { - PdfNumber? top; + IPdfPrimitive? top; if (array.count >= 3) { - top = array[2]! as PdfNumber; + top = array[2]; } if (page != null) { - final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + final double topValue = (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; _destination = PdfDestination(page, Offset(0, topValue)); _destination!.mode = PdfDestinationMode.fitH; } @@ -963,56 +966,56 @@ class PdfBookmark extends PdfBookmarkBase { } } if (array != null) { - final PdfReferenceHolder? holder = array[0] as PdfReferenceHolder?; + final IPdfPrimitive? holder = array[0]; PdfPage? page; - if (holder != null) { - final PdfDictionary? dic = - _helper._crossTable.getObject(holder) as PdfDictionary?; - if (dic != null && ldDoc != null) { + if (holder != null && holder is PdfReferenceHolder) { + final IPdfPrimitive? dic = _helper._crossTable.getObject(holder); + if (dic != null && ldDoc != null && dic is PdfDictionary) { page = PdfPageCollectionHelper.getHelper(ldDoc.pages).getPage(dic); } } - - PdfName? mode; + IPdfPrimitive? mode; if (array.count > 1) { - mode = array[1]! as PdfName; + mode = array[1]; } - if (mode != null) { + if (mode != null && mode is PdfName) { if (mode.name == PdfDictionaryProperties.fitBH || mode.name == PdfDictionaryProperties.fitH) { - PdfNumber? top; + IPdfPrimitive? top; if (array.count >= 3) { - top = array[2]! as PdfNumber; + top = array[2]; } if (page != null) { - final double topValue = - (top == null) ? 0 : page.size.height - top.value!; + final double topValue = (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; _destination = PdfDestination(page, Offset(0, topValue)); _destination!.mode = PdfDestinationMode.fitH; } } else if (mode.name == PdfDictionaryProperties.xyz) { - PdfNumber? left; - PdfNumber? top; + IPdfPrimitive? left; + IPdfPrimitive? top; if (array.count > 2) { - left = array[2]! as PdfNumber; + left = array[2]; } if (array.count > 3) { - top = array[3]! as PdfNumber; + top = array[3]; } - PdfNumber? zoom; - if (array.count > 4 && (array[4] is PdfNumber)) { - zoom = array[4]! as PdfNumber; + IPdfPrimitive? zoom; + if (array.count > 4) { + zoom = array[4]; } - if (page != null) { - final double topValue = - (top == null) ? 0 : page.size.height - top.value!; - final double leftValue = - (left == null) ? 0 : left.value! as double; + final double topValue = (top != null && top is PdfNumber) + ? page.size.height - top.value! + : 0; + final double leftValue = (left != null && left is PdfNumber) + ? left.value! as double + : 0; _destination = PdfDestination(page, Offset(leftValue, topValue)); - if (zoom != null) { - _destination!.zoom = zoom.value!.toDouble(); - } + _destination!.zoom = (zoom != null && zoom is PdfNumber) + ? zoom.value!.toDouble() + : 0; } } else { if (page != null && mode.name == PdfDictionaryProperties.fit) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart index 413c13fc6..7fd2c6bb6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/pdf_document.dart @@ -1,10 +1,23 @@ import 'dart:collection'; import 'dart:convert'; +import 'package:xml/xml.dart'; + import '../../interfaces/pdf_interface.dart'; +import '../annotations/enum.dart'; +import '../annotations/fdf_document.dart'; +import '../annotations/fdf_parser.dart'; +import '../annotations/json_document.dart'; +import '../annotations/json_parser.dart'; +import '../annotations/pdf_action_annotation.dart'; +import '../annotations/pdf_annotation.dart'; +import '../annotations/pdf_annotation_collection.dart'; +import '../annotations/pdf_text_web_link.dart'; +import '../annotations/xfdf_parser.dart'; import '../color_space/pdf_icc_color_profile.dart'; import '../forms/pdf_form.dart'; import '../forms/pdf_form_field_collection.dart'; +import '../forms/pdf_xfdf_document.dart'; import '../general/file_specification_base.dart'; import '../general/pdf_destination.dart'; import '../general/pdf_named_destination.dart'; @@ -773,6 +786,67 @@ class PdfDocument { _helper.currentSavingObject = null; } + /// Export the annotation data to UTF8 bytes with the specific [PdfAnnotationDataFormat]. + /// + /// To export specific annotations, annotation types, appearances, and + /// add a file name to the export format, we can use the named parameters + /// exportList, exportTypes, exportAppearance, and fileName respectively. + /// + /// ```dart + /// //Load an existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// //Export annotations in specific PdfAnnotationDataFormat format. + /// List bytes = document.exportAnnotation(PdfAnnotationDataFormat.fdf, + /// fileName: 'PDFExportDocument'); + /// //Save the exported data. + /// File('export.fdf').writeAsBytesSync(bytes); + /// //Dispose the document. + /// document.dispose(); + /// ``` + List exportAnnotation(PdfAnnotationDataFormat format, + {String? fileName, + List? exportList, + List? exportTypes, + bool exportAppearance = false}) { + List bytes = []; + if (format == PdfAnnotationDataFormat.xfdf) { + bytes = _helper.exportXfdf( + fileName, exportList, exportTypes, exportAppearance); + } else if (format == PdfAnnotationDataFormat.fdf) { + bytes = _helper.exportFdf( + fileName, exportList, exportTypes, exportAppearance); + } else if (format == PdfAnnotationDataFormat.json) { + bytes = _helper.exportJson( + fileName, exportList, exportTypes, exportAppearance); + } + return bytes; + } + + /// Import the annotation data from UTF8 bytes with the specific [PdfAnnotationDataFormat]. + /// + /// ```dart + /// //Load an existing PDF document. + /// PdfDocument document = + /// PdfDocument(inputBytes: File('input.pdf').readAsBytesSync()); + /// //Import annotations in specific PdfAnnotationDataFormat format. + /// document.importAnnotation( + /// File('input.fdf').readAsBytesSync(), PdfAnnotationDataFormat.fdf); + /// //Save the document. + /// File('output.pdf').writeAsBytesSync(await document.save()); + /// //Dispose the document. + /// document.dispose(); + /// ``` + void importAnnotation(List data, PdfAnnotationDataFormat format) { + if (format == PdfAnnotationDataFormat.xfdf) { + _helper.importXfdf(data); + } else if (format == PdfAnnotationDataFormat.fdf) { + _helper.importFdf(data); + } else if (format == PdfAnnotationDataFormat.json) { + _helper.importJson(data); + } + } + //Implementation void _initialize(List? pdfData) { _helper._isAttachOnlyEncryption = false; @@ -1080,10 +1154,10 @@ class PdfDocument { } Future _onDocumentSavedAsync(DocumentSavedArgs args) async { - if (_helper.documentSavedList != null && - _helper.documentSavedList!.isNotEmpty) { - for (int i = 0; i < _helper.documentSavedList!.length; i++) { - _helper.documentSavedList![i](this, args); + if (_helper.documentSavedListAsync != null && + _helper.documentSavedListAsync!.isNotEmpty) { + for (int i = 0; i < _helper.documentSavedListAsync!.length; i++) { + await _helper.documentSavedListAsync![i](this, args); } } } @@ -1200,6 +1274,9 @@ class PdfDocumentHelper { /// internal field List? documentSavedList; + /// internal field + List? documentSavedListAsync; + /// internal field late PdfMainObjectCollection objects; @@ -1391,15 +1468,17 @@ class PdfDocumentHelper { } } else { final PdfDestination? dest = (current as PdfBookmark).destination; - final PdfPage page = dest!.page; - List? list = _bookmarkHashTable!.containsKey(page) - ? _bookmarkHashTable![page] as List? - : null; - if (list == null) { - list = []; - _bookmarkHashTable![page] = list; + if (dest != null) { + final PdfPage page = dest.page; + List? list = _bookmarkHashTable!.containsKey(page) + ? _bookmarkHashTable![page] as List? + : null; + if (list == null) { + list = []; + _bookmarkHashTable![page] = list; + } + list.add(current); } - list.add(current); } ni.index = ni.index + 1; if (current.count > 0) { @@ -1423,4 +1502,281 @@ class PdfDocumentHelper { void setUserPassword(PdfPasswordArgs args) { base.onPdfPassword!(base, args); } + + /// Imports the FDF file bytes. + void importFdf(List inputBytes) { + final FdfParser parser = FdfParser(inputBytes); + parser.parseAnnotationData(); + parser.importAnnotations(base); + parser.dispose(); + } + + /// Imports the FDF file bytes. + void importJson(List inputBytes) { + final JsonParser parser = JsonParser(base); + parser.importAnnotationData(inputBytes); + } + + /// Imports the XFDF file bytes. + void importXfdf(List data) { + final XfdfParser parser = XfdfParser(data, base); + parser.parseAndImportAnnotationData(); + } + + /// Exports annotation to FDF file bytes. + List exportFdf(String? fileName, List? exportAnnotation, + List? exportTypes, bool exportAppearance) { + const String genNumber = + '${PdfOperators.whiteSpace}0${PdfOperators.whiteSpace}'; + const String startDictionary = '<<${PdfOperators.slash}'; + final List fdfBytes = []; + fdfBytes.addAll(utf8.encode('%FDF-1.2${PdfOperators.newLine}')); + int currentID = 2; + final List annotID = []; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + for (int i = 0; i < exportAnnotation.length; i++) { + final PdfAnnotation annotation = exportAnnotation[i]; + final PdfAnnotationHelper helper = + PdfAnnotationHelper.getHelper(annotation); + if (helper.isLoadedAnnotation && + (annotType.isEmpty || + annotType.contains(_getAnnotationType(helper.dictionary!))) && + !(annotation is PdfLinkAnnotation || + annotation is PdfTextWebLink) && + annotation.page != null) { + final FdfDocument fdfDocument = + FdfDocument(helper.dictionary!, annotation.page!); + final Map result = fdfDocument.exportAnnotations( + currentID, + annotID, + base.pages.indexOf(annotation.page!), + _checkForStamp(helper.dictionary!) == 'Stamp' || + exportAppearance); + fdfBytes.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPage page = base.pages[i]; + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(page); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if ((annotType.isEmpty || + annotType + .contains(_getAnnotationType(annotationDictionary))) && + !isLinkAnnotation(annotationDictionary)) { + final FdfDocument fdfDocument = + FdfDocument(annotationDictionary, page); + final Map result = fdfDocument.exportAnnotations( + currentID, + annotID, + i, + _checkForStamp(annotationDictionary) == 'Stamp' || + exportAppearance); + fdfBytes.addAll(result['exportData'] as List); + currentID = result['currentID'] as int; + } + } + } + } + fileName ??= ''; + if (currentID != 2) { + const String root = '1$genNumber'; + fdfBytes.addAll(utf8.encode( + '${'$root${PdfOperators.obj}${PdfOperators.newLine}${startDictionary}FDF$startDictionary${PdfDictionaryProperties.annots}'}[')); + for (int i = 0; i < annotID.length - 1; i++) { + fdfBytes.addAll(utf8.encode( + '${annotID[i]}$genNumber${PdfDictionaryProperties.r}${PdfOperators.whiteSpace}')); + } + fdfBytes.addAll(utf8.encode( + '${annotID[annotID.length - 1]}$genNumber${PdfDictionaryProperties.r}]${PdfOperators.slash}${PdfDictionaryProperties.f}($fileName)${PdfOperators.slash}${PdfDictionaryProperties.uf}($fileName)>>${PdfOperators.slash}${PdfDictionaryProperties.type}${PdfOperators.slash}${PdfDictionaryProperties.catalog}>>${PdfOperators.newLine}${PdfOperators.endobj}${PdfOperators.newLine}')); + fdfBytes.addAll(utf8.encode( + '${PdfOperators.trailer}${PdfOperators.newLine}$startDictionary${PdfDictionaryProperties.root}${PdfOperators.whiteSpace}$root${PdfDictionaryProperties.r}>>${PdfOperators.newLine}${PdfOperators.endOfFileMarker}${PdfOperators.newLine}')); + } + return fdfBytes; + } + + /// Exports annotation to JSON file bytes. + List exportJson(String? fileName, List? exportAnnotation, + List? exportTypes, bool exportAppearance) { + String json = '{"pdfAnnotation":{'; + bool isAnnotationAdded = false; + JsonDocument? jsonDocument = JsonDocument(base); + final Map table = {}; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + final Map table1 = {}; + String? tempJson = ''; + for (int j = 0; j < exportAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + PdfAnnotationHelper.getHelper(exportAnnotation[j]).dictionary!; + if ((annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) && + exportAnnotation[j].page != null) { + final int pageIndex = base.pages.indexOf(exportAnnotation[j].page!); + if (pageIndex >= 0) { + if (table1.containsKey(pageIndex)) { + tempJson = '${table1[pageIndex]!},'; + } else { + tempJson = '"$pageIndex":{ "shapeAnnotation":['; + } + jsonDocument.exportAnnotationData( + table, + exportAppearance, + base.pages.indexOf(exportAnnotation[j].page!), + annotationDictionary); + tempJson += jsonDocument.convertToJson(table); + table1[pageIndex] = tempJson; + table.clear(); + } + } + } + final List values = table1.values.toList(); + for (int i = 0; i < values.length; i++) { + json += table1[i]! + ((i < values.length - 1) ? ']},' : ']}'); + } + table1.clear(); + values.clear(); + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(base.pages[i]); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + if (pageHelper.terminalAnnotation.isNotEmpty) { + json += (i != 0 && isAnnotationAdded) ? ',' : ' '; + json += '"$i":{ "shapeAnnotation":['; + isAnnotationAdded = true; + } + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if (annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) { + jsonDocument.exportAnnotationData( + table, exportAppearance, i, annotationDictionary); + json += jsonDocument.convertToJson(table); + if (j < pageHelper.terminalAnnotation.length - 1) { + json += ','; + } + table.clear(); + } + } + if (pageHelper.terminalAnnotation.isNotEmpty) { + json += ']}'; + } + } + } + jsonDocument = null; + json += '}}'; + return utf8.encode(json); + } + + /// Exports annotation to XFDF file bytes. + List exportXfdf(String? fileName, List? exportAnnotation, + List? exportTypes, bool exportAppearance) { + final XFdfDocument xfdf = XFdfDocument(fileName ?? ''); + final List elements = []; + final List annotType = []; + _getExportTypes(exportTypes, annotType); + if (exportAnnotation != null && exportAnnotation.isNotEmpty) { + for (int j = 0; j < exportAnnotation.length; j++) { + if (annotType.isEmpty || + annotType.contains(_getAnnotationType( + PdfAnnotationHelper.getHelper(exportAnnotation[j]) + .dictionary!))) { + final XmlElement? element = xfdf.exportAnnotationData( + PdfAnnotationHelper.getHelper(exportAnnotation[j]).dictionary!, + base.pages.indexOf(exportAnnotation[j].page!), + exportAppearance, + base); + if (element != null) { + elements.add(element); + } + } + } + } else { + for (int i = 0; i < base.pages.count; i++) { + final PdfPageHelper pageHelper = PdfPageHelper.getHelper(base.pages[i]); + pageHelper.createAnnotations(pageHelper.getWidgetReferences()); + for (int j = 0; j < pageHelper.terminalAnnotation.length; j++) { + final PdfDictionary annotationDictionary = + pageHelper.terminalAnnotation[j]; + if (annotType.isEmpty || + annotType.contains(_getAnnotationType(annotationDictionary))) { + final XmlElement? element = xfdf.exportAnnotationData( + annotationDictionary, i, exportAppearance, base); + if (element != null) { + elements.add(element); + } + } + } + } + } + return xfdf.save(elements); + } + + void _getExportTypes( + List? types, List annotType) { + if (types != null && types.isNotEmpty) { + for (int i = 0; i < types.length; i++) { + String annotationType = getEnumName(types[i]); + switch (annotationType) { + case 'HighlightAnnotation': + annotationType = 'Highlight'; + break; + case 'UnderlineAnnotation': + annotationType = 'Underline'; + break; + case 'StrikeOutAnnotation': + annotationType = 'StrikeOut'; + break; + case 'SquigglyAnnotation': + annotationType = 'Squiggly'; + break; + } + annotType.add(annotationType); + } + } + } + + String _getAnnotationType(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final PdfName name = PdfAnnotationHelper.getValue( + dictionary, crossTable, PdfDictionaryProperties.subtype, true)! + as PdfName; + final PdfAnnotationTypes type = + PdfAnnotationCollectionHelper.getAnnotationType( + name, dictionary, crossTable); + return getEnumName(type); + } + return ''; + } + + String _checkForStamp(PdfDictionary dictionary) { + if (dictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + dictionary[PdfDictionaryProperties.subtype]); + if (name != null && name is PdfName) { + return name.name ?? ''; + } + } + return ''; + } + + /// Internal method. + static bool isLinkAnnotation(PdfDictionary annotationDictionary) { + if (annotationDictionary.containsKey(PdfDictionaryProperties.subtype)) { + final IPdfPrimitive? name = PdfCrossTable.dereference( + annotationDictionary[PdfDictionaryProperties.subtype]); + if (name != null && name is PdfName) { + return name.name == 'Link'; + } + } + return false; + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart index 1867e75d9..40aa315bd 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_dictionary.dart @@ -178,7 +178,10 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { } /// internal method - void setName(PdfName key, String? name) { + void setName(dynamic key, String? name) { + if (key is String) { + key = PdfName(key); + } if (items!.containsKey(key)) { this[key] = PdfName(name); modify(); @@ -204,10 +207,12 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { /// internal method void setString(String key, String? str) { - final PdfString? pdfString = this[key] as PdfString?; - if (pdfString != null) { - pdfString.value = str; - modify(); + if (containsKey(key) && this[key] is PdfString) { + final IPdfPrimitive? pdfString = this[key]; + if (pdfString != null && pdfString is PdfString) { + pdfString.value = str; + modify(); + } } else { this[key] = PdfString(str!); } @@ -359,7 +364,7 @@ class PdfDictionary implements IPdfPrimitive, IPdfChangable { } /// internal method - void setNumber(String key, int? value) { + void setNumber(String key, num? value) { final PdfNumber? pdfNumber = this[key] as PdfNumber?; if (pdfNumber != null) { pdfNumber.value = value; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart index 96ad56e8e..8e11b1cae 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_name.dart @@ -62,7 +62,7 @@ class PdfName implements IPdfPrimitive { /// Replace the hexa decimal format to replace characters. static String? decodeName(String? value) { - if (value != null) + if (value != null) { return value .replaceAll('#9', '\t') .replaceAll('#09', '\t') @@ -75,6 +75,7 @@ class PdfName implements IPdfPrimitive { .replaceAll('#0D', '\r') .replaceAll('#0d', '\r') .replaceAll('#20', ' '); + } return null; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart index 33c399673..2fd621e7f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_stream.dart @@ -51,10 +51,6 @@ class PdfStream extends PdfDictionary { bool? _compress; PdfStream? _clonedObject; - /// internal field - @override - bool? isChanged; - /// internal field late bool blockEncryption; @@ -82,7 +78,7 @@ class PdfStream extends PdfDictionary { outputStream, false, document.compressionLevel, false); compressedWriter.write(data!, 0, data.length, false); compressedWriter.close(); - _addFilter(PdfDictionaryProperties.flateDecode); + addFilter(PdfDictionaryProperties.flateDecode); compress = false; return outputStream; } else { @@ -102,7 +98,7 @@ class PdfStream extends PdfDictionary { clearStream(); compress = false; dataStream = outputStream; - _addFilter(PdfDictionaryProperties.flateDecode); + addFilter(PdfDictionaryProperties.flateDecode); } } return dataStream; @@ -267,7 +263,8 @@ class PdfStream extends PdfDictionary { return data; } - void _addFilter(String filterName) { + /// Internal method. + void addFilter(String filterName) { IPdfPrimitive? filter = this[PdfDictionaryProperties.filter]; if (filter is PdfReferenceHolder) { filter = filter.referenceObject; @@ -368,7 +365,7 @@ class PdfStream extends PdfDictionary { } attachmentEncrypted = true; data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); } if (!containsKey(PdfDictionaryProperties.decodeParms)) { final PdfArray decode = PdfArray(); @@ -410,7 +407,7 @@ class PdfStream extends PdfDictionary { data = _compressStream(); } data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); } } } @@ -421,7 +418,7 @@ class PdfStream extends PdfDictionary { data = _compressStream(); } data = _encryptContent(data, writer); - _addFilter(PdfDictionaryProperties.crypt); + addFilter(PdfDictionaryProperties.crypt); if (!containsKey(PdfDictionaryProperties.decodeParms)) { final PdfArray decode = PdfArray(); final PdfDictionary decodeparms = PdfDictionary(); @@ -487,9 +484,6 @@ class PdfStream extends PdfDictionary { return newStream; } - @override - bool? decrypted; - /// internal method void decrypt(PdfEncryptor encryptor, int? currentObjectNumber) { if (!decrypted!) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart index 5fc946d91..7c78b65d0 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/primitives/pdf_string.dart @@ -155,12 +155,18 @@ class PdfString implements IPdfPrimitive { /// internal method static String bytesToHex(List data) { - String result = ''; - for (int i = 0; i < data.length; i++) { - final String radix = data[i].toRadixString(16); - result += (radix.length == 1 ? '0$radix' : radix).toUpperCase(); - } - return result; + return data + .map((int byte) => byte.toRadixString(16).padLeft(2, '0')) + .join() + .toUpperCase(); + } + + /// internal method + static Future bytesToHexAsync(List data) async { + return data + .map((int byte) => byte.toRadixString(16).padLeft(2, '0')) + .join() + .toUpperCase(); } /// internal method diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart index 576c1357d..d21fee74a 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1.dart @@ -316,6 +316,19 @@ abstract class Asn1Encode implements IAsn1 { } } + /// internal method + Future?> getEncodedAsync([String? encoding]) async { + if (encoding == null) { + return (Asn1DerStream([])..writeObject(this)).stream; + } else { + if (encoding == Asn1.der) { + final DerStream stream = DerStream([])..writeObject(this); + return stream.stream; + } + return getEncoded(); + } + } + /// internal method List? getDerEncoded() { return getEncoded(Asn1.der); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart index 5ecb55e37..bff05eb19 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_parser.dart @@ -115,8 +115,9 @@ class Asn1Parser { } final Asn1LengthStream stream = Asn1LengthStream(_stream, _limit); final Asn1Parser helper = Asn1Parser(stream, _limit); - if ((tag & Asn1Tags.tagged) != 0) + if ((tag & Asn1Tags.tagged) != 0) { return BerTagHelper(true, tagNumber, helper); + } return helper.readIndefinite(tagNumber); } else { final Asn1StreamHelper stream = Asn1StreamHelper(_stream, length); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart index ea94693c3..8630b8700 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/asn1_stream.dart @@ -139,8 +139,9 @@ class Asn1Stream { Asn1? buildObject(int tag, int tagNumber, int length) { final bool isConstructed = (tag & Asn1Tags.constructed) != 0; final Asn1StreamHelper stream = Asn1StreamHelper(_stream, length); - if ((tag & Asn1Tags.tagged) != 0) + if ((tag & Asn1Tags.tagged) != 0) { return Asn1Parser(stream).readTaggedObject(isConstructed, tagNumber); + } if (isConstructed) { switch (tagNumber) { case Asn1Tags.octetString: @@ -379,7 +380,7 @@ class Asn1StreamHelper extends Asn1BaseStream { /// internal class class Asn1LengthStream extends Asn1BaseStream { /// internal constructor - Asn1LengthStream(PdfStreamReader? stream, int? limit) : super(stream, limit) { + Asn1LengthStream(super.stream, super.limit) { byte = requireByte(); checkEndOfFile(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart index b7043787c..43a8eb286 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/ber.dart @@ -8,7 +8,7 @@ import 'der.dart'; /// internal class class BerOctet extends DerOctet { /// internal constructor - BerOctet(List bytes) : super(bytes); + BerOctet(super.bytes); /// internal constructor BerOctet.fromCollection(List octets) @@ -160,11 +160,10 @@ class BerTagHelper implements IAsn1Tag { /// internal class class BerSequence extends DerSequence { /// internal constructor - BerSequence({List? array, Asn1EncodeCollection? collection}) - : super(array: array, collection: collection); + BerSequence({List? super.array, super.collection}); /// internal constructor - BerSequence.fromObject(Asn1Encode? object) : super.fromObject(object); + BerSequence.fromObject(super.object) : super.fromObject(); /// internal constructor static BerSequence empty = BerSequence(); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart index d245e40c7..e2ec6f4e4 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/asn1/der.dart @@ -464,10 +464,6 @@ class DerNull extends Asn1Null { bytes = []; } //Fields - /// internal field - @override - List? bytes; - /// internal field static DerNull value = DerNull(); //Implementation @@ -509,9 +505,6 @@ class DerObjectID extends Asn1 { /// internal field String? id; - /// internal field - @override - List? bytes; // ignore: prefer_final_fields static List _objects = List.generate(1024, (int i) => null); @@ -714,10 +707,10 @@ class DerObjectID extends Asn1 { /// internal class class DerOctet extends Asn1Octet { /// internal constructor - DerOctet(List bytes) : super(bytes); + DerOctet(List super.bytes); /// internal constructor - DerOctet.fromObject(Asn1Encode asn1) : super.fromObject(asn1); + DerOctet.fromObject(super.asn1) : super.fromObject(); @override void encode(DerStream stream) { stream.writeEncoded(Asn1Tags.octetString, value); @@ -919,8 +912,7 @@ class DerStream { /// internal class class DerTag extends Asn1Tag { /// internal constructor - DerTag(int? tagNumber, Asn1Encode? asn1, [bool? isExplicit]) - : super(tagNumber, asn1) { + DerTag(super.tagNumber, super.asn1, [bool? isExplicit]) { if (isExplicit != null) { explicit = isExplicit; } @@ -994,12 +986,10 @@ class DerUtcTime extends Asn1 { /// internal class class DerCatalogue extends Asn1 { /// internal constructor - DerCatalogue(this.bytes); - //Fields + DerCatalogue(List? bytes) { + this.bytes = bytes; + } - /// internal field - @override - List? bytes; //Implemnetation @override // ignore: avoid_renaming_method_parameters diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart index 0300badb4..fa386be6d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/cipher_block_chaining_mode.dart @@ -197,8 +197,7 @@ class CipherParameter implements ICipherParameter { /// internal class class RsaKeyParam extends CipherParameter { /// internal constructor - RsaKeyParam(bool isPrivate, BigInt? modulus, BigInt? exponent) - : super(isPrivate) { + RsaKeyParam(super.isPrivate, BigInt? modulus, BigInt? exponent) { _modulus = modulus; _exponent = exponent; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart index edd97da9b..e518af6b2 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_external_signer.dart @@ -11,8 +11,13 @@ class IPdfExternalSigner { DigestAlgorithm get hashAlgorithm => _hashAlgorithm; //Public methods - /// Returns Signed Message Digest. - SignerResult? sign(List message) { + /// Asynchronously returns signed message digest. + Future sign(List message) async { + return null; + } + + /// Synchronously returns signed message digest. + SignerResult? signSync(List message) { return null; } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart index aef85c270..356dd9250 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dart @@ -434,6 +434,22 @@ class PdfPKCSCertificate { return result; } + /// internal method + Future> getContentTableAsync() async { + final Map result = {}; + // ignore: avoid_function_literals_in_foreach_calls + _certificates.keys.forEach((String key) { + result[key] = 'cert'; + }); + // ignore: avoid_function_literals_in_foreach_calls + _keys.keys.forEach((String key) { + if (!result.containsKey(key)) { + result[key] = 'key'; + } + }); + return result; + } + /// internal method bool isKey(String key) { return _keys[key] != null; @@ -471,6 +487,33 @@ class PdfPKCSCertificate { } } + /// internal method + Future getCertificateAsync(String key) async { + dynamic certificates = _certificates[key]; + if (certificates != null && certificates is X509Certificates) { + return certificates; + } else { + String? id; + if (_localIdentifiers.containsKey(key)) { + id = _localIdentifiers[key]; + } + if (id != null) { + if (_keyCertificates.containsKey(id)) { + certificates = _keyCertificates[id] is X509Certificates + ? _keyCertificates[id] + : null; + } + } else { + if (_keyCertificates.containsKey(key)) { + certificates = _keyCertificates[key] is X509Certificates + ? _keyCertificates[key] + : null; + } + } + return certificates as X509Certificates?; + } + } + /// internal method List? getCertificateChain(String key) { if (!isKey(key)) { @@ -535,6 +578,73 @@ class PdfPKCSCertificate { } return null; } + + /// internal method + Future?> getCertificateChainAsync(String key) async { + if (!isKey(key)) { + return null; + } + List? x509Certificates; + await getCertificateAsync(key).then((X509Certificates? certificates) { + if (certificates != null) { + final List certificateList = []; + bool isContinue = true; + while (certificates != null) { + final X509Certificate x509Certificate = certificates.certificate!; + X509Certificates? nextCertificate; + final Asn1Octet? x509Extension = x509Certificate + .getExtension(X509Extensions.authorityKeyIdentifier); + if (x509Extension != null) { + final _KeyIdentifier id = _KeyIdentifier.getKeyIdentifier( + Asn1Stream(PdfStreamReader(x509Extension.getOctets())) + .readAsn1()); + if (id.keyID != null) { + if (_chainCertificates + .containsKey(_CertificateIdentifier(id: id.keyID))) { + nextCertificate = + _chainCertificates[_CertificateIdentifier(id: id.keyID)]; + } + } + } + if (nextCertificate == null) { + final X509Name? issuer = x509Certificate.c!.issuer; + final X509Name? subject = x509Certificate.c!.subject; + if (!(issuer == subject)) { + final List<_CertificateIdentifier> keys = + _chainCertificates.keys.toList(); + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((_CertificateIdentifier certId) { + X509Certificates? x509CertEntry; + if (_chainCertificates.containsKey(certId)) { + x509CertEntry = _chainCertificates[certId]; + } + final X509Certificate certificate = x509CertEntry!.certificate!; + if (certificate.c!.subject == issuer) { + try { + // x509Certificate.verify(certificate.getPublicKey()); + // nextCertificate = x509CertEntry; + isContinue = false; + } catch (e) { + // + } + } + }); + } + } + if (isContinue) { + certificateList.add(certificates); + certificates = + nextCertificate != null && nextCertificate != certificates + ? nextCertificate + : null; + } + } + x509Certificates = List.generate( + certificateList.length, (int i) => certificateList[i]); + } + }); + return x509Certificates; + } } class _CertificateTable { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart index 4a33ec0a9..472433003 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:math'; @@ -41,11 +42,12 @@ class PdfSignatureDictionary implements IPdfWrapper { PdfSignatureDictionary(PdfDocument doc, PdfSignature sig) { _doc = doc; _sig = sig; - if (PdfDocumentHelper.getHelper(doc).documentSavedList == null) { - PdfDocumentHelper.getHelper(doc).documentSavedList = - []; - } - PdfDocumentHelper.getHelper(doc).documentSavedList!.add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedList ??= + []) + .add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedListAsync ??= + []) + .add(_documentSavedAsync); dictionary!.beginSaveList ??= []; dictionary!.beginSaveList!.add(_dictionaryBeginSave); _cert = sig.certificate; @@ -54,10 +56,12 @@ class PdfSignatureDictionary implements IPdfWrapper { /// internal constructor PdfSignatureDictionary.fromDictionary(PdfDocument doc, this.dictionary) { _doc = doc; - List? documentSavedList = - PdfDocumentHelper.getHelper(doc).documentSavedList; - documentSavedList ??= []; - documentSavedList.add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedList ??= + []) + .add(_documentSaved); + (PdfDocumentHelper.getHelper(doc).documentSavedListAsync ??= + []) + .add(_documentSavedAsync); dictionary!.beginSaveList ??= []; dictionary!.beginSaveList!.add(_dictionaryBeginSave); } @@ -334,6 +338,57 @@ class PdfSignatureDictionary implements IPdfWrapper { PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt = enabled; } + Future _documentSavedAsync(Object sender, DocumentSavedArgs e) async { + final bool enabled = + PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt; + PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt = false; + final PdfWriter writer = e.writer! as PdfWriter; + final int number = e.writer!.length! - _secondRangeIndex!; + const String str = '0 '; + final String str2 = '$_firstRangeLength '; + final String str3 = '$_secondRangeIndex '; + final String str4 = number.toString(); + await _saveRangeItemAsync(writer, str, _startPositionByteRange!) + .then((int startPosition) async { + await _saveRangeItemAsync(writer, str2, startPosition) + .then((int startPosition) async { + await _saveRangeItemAsync(writer, str3, startPosition) + .then((int startPosition) async { + await _saveRangeItemAsync(e.writer! as PdfWriter, str4, startPosition) + .then((int startPosition) async { + _range = [ + 0, + int.parse(str2), + int.parse(str3), + int.parse(str4) + ]; + _stream = writer.buffer; + await getPkcs7ContentAsync().then((List? value) async { + await PdfString.bytesToHexAsync(value!).then((String text) async { + _stream!.replaceRange(_firstRangeLength!, + _firstRangeLength! + 1, utf8.encode('<')); + final int newPos = _firstRangeLength! + 1 + text.length; + _stream!.replaceRange( + _firstRangeLength! + 1, newPos, utf8.encode(text)); + final int num3 = (_secondRangeIndex! - newPos) ~/ 2; + await PdfString.bytesToHexAsync( + List.generate(num3, (int i) => 0)) + .then((String emptyText) async { + _stream!.replaceRange(newPos, newPos + emptyText.length, + utf8.encode(emptyText)); + _stream!.replaceRange(newPos + emptyText.length, + newPos + emptyText.length + 1, utf8.encode('>')); + PdfSecurityHelper.getHelper(_doc.security).encryptor.encrypt = + enabled; + }); + }); + }); + }); + }); + }); + }); + } + /// internal method List? getPkcs7Content() { String? hasalgorithm = ''; @@ -402,7 +457,7 @@ class PdfSignatureDictionary implements IPdfWrapper { .getEncoded(Asn1.der); List? extSignature; if (externalSigner != null) { - final SignerResult? signerResult = externalSigner.sign(sh!); + final SignerResult? signerResult = externalSigner.signSync(sh!); if (signerResult != null && signerResult.signedData.isNotEmpty) { extSignature = signerResult.signedData; } @@ -421,6 +476,113 @@ class PdfSignatureDictionary implements IPdfWrapper { _sig!.cryptographicStandard, hasalgorithm); } + /// internal method + Future?> getPkcs7ContentAsync() async { + List? pkcs7Content; + String? hasalgorithm = ''; + _SignaturePrivateKey? externalSignature; + List>? crlBytes; + List? ocspByte; + List? chain = []; + final IPdfExternalSigner? externalSigner = + PdfSignatureHelper.getHelper(_sig!).externalSigner; + if (externalSigner != null && + PdfSignatureHelper.getHelper(_sig!).externalChain != null) { + chain = PdfSignatureHelper.getHelper(_sig!).externalChain; + final String digest = getDigestAlgorithm(externalSigner.hashAlgorithm); + final _SignaturePrivateKey pks = _SignaturePrivateKey(digest); + hasalgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + } else { + String certificateAlias = ''; + await PdfCertificateHelper.getPkcsCertificate(_cert!) + .getContentTableAsync() + .then((Map contentTable) async { + final List keys = contentTable.keys.toList(); + bool isContinue = true; + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((String key) { + if (isContinue && + PdfCertificateHelper.getPkcsCertificate(_cert!).isKey(key) && + PdfCertificateHelper.getPkcsCertificate(_cert!) + .getKey(key)! + .key! + .isPrivate!) { + certificateAlias = key; + isContinue = false; + } + }); + final KeyEntry pk = PdfCertificateHelper.getPkcsCertificate(_cert!) + .getKey(certificateAlias)!; + await PdfCertificateHelper.getPkcsCertificate(_cert!) + .getCertificateChainAsync(certificateAlias) + .then((List? certificates) { + // ignore: avoid_function_literals_in_foreach_calls + certificates!.forEach((X509Certificates c) { + chain!.add(c.certificate); + }); + final RsaPrivateKeyParam? parameters = pk.key as RsaPrivateKeyParam?; + final String digest = _sig != null + ? getDigestAlgorithm(_sig!.digestAlgorithm) + : MessageDigestAlgorithms.secureHash256; + final _SignaturePrivateKey pks = + _SignaturePrivateKey(digest, parameters); + hasalgorithm = pks.getHashAlgorithm(); + externalSignature = pks; + }); + }); + } + final _PdfCmsSigner pkcs7 = + _PdfCmsSigner(null, chain!, hasalgorithm!, false); + final IRandom source = getUnderlyingSource(); + final List sources = + List.generate(_range.length ~/ 2, (int i) => null); + for (int j = 0; j < _range.length; j += 2) { + sources[j ~/ 2] = _WindowRandom(source, _range[j], _range[j + 1]); + } + final PdfStreamReader data = _RandomStream(_RandomGroup(sources)); + await pkcs7._digestAlgorithm + .digestAsync(data, hasalgorithm) + .then((List? hash) async { + await pkcs7 + .getSequenceDataSetAsync( + hash!, ocspByte, crlBytes, _sig!.cryptographicStandard) + .then((DerSet derSet) async { + await derSet.getEncodedAsync(Asn1.der).then((List? sh) async { + List? extSignature; + if (externalSigner != null) { + await externalSigner + .sign(sh!) + .then((SignerResult? signerResult) async { + signerResult ??= externalSigner.signSync(sh); + if (signerResult != null && signerResult.signedData.isNotEmpty) { + extSignature = signerResult.signedData; + } + if (extSignature != null) { + await pkcs7.setSignedDataAsync(extSignature!, null, + externalSignature!.getEncryptionAlgorithm()); + } else { + pkcs7Content = + List.filled(_estimatedSize, 0, growable: true); + } + }); + } else { + await externalSignature! + .signAsync(sh!) + .then((List? value) => extSignature = value); + } + if (pkcs7Content == null) { + await pkcs7.setSignedDataAsync(extSignature!, null, + externalSignature!.getEncryptionAlgorithm()); + pkcs7Content = await pkcs7.signAsync(hash, null, null, ocspByte, + crlBytes, _sig!.cryptographicStandard, hasalgorithm); + } + }); + }); + }); + return pkcs7Content; + } + /// internal method IRandom getUnderlyingSource() { return _RandomArray(_stream!.sublist(0)); @@ -454,6 +616,14 @@ class PdfSignatureDictionary implements IPdfWrapper { return startPosition + str.length; } + Future _saveRangeItemAsync( + PdfWriter writer, String str, int startPosition) async { + final List date = utf8.encode(str); + writer.buffer! + .replaceRange(startPosition, startPosition + date.length, date); + return startPosition + str.length; + } + /// internal property IPdfPrimitive? get element => dictionary; set element(IPdfPrimitive? value) { @@ -550,6 +720,18 @@ class MessageDigestAlgorithms { return result; } + /// internal method + Future getAllowedDigestsAsync(String name) async { + String? result; + final String lower = name.toLowerCase(); + _digests.forEach((String key, String value) { + if (lower == key.toLowerCase()) { + result = _digests[key]; + } + }); + return result; + } + /// internal method dynamic getMessageDigest(String hashAlgorithm) { String lower = hashAlgorithm.toLowerCase(); @@ -580,6 +762,36 @@ class MessageDigestAlgorithms { return result; } + /// internal method + Future getMessageDigestAsync(String hashAlgorithm) async { + String lower = hashAlgorithm.toLowerCase(); + String? digest = lower; + bool isContinue = true; + _algorithms.forEach((String? key, String value) { + if (isContinue && key!.toLowerCase() == lower) { + digest = _algorithms[key]; + isContinue = false; + } + }); + dynamic result; + lower = digest!.toLowerCase(); + if (lower == 'sha1' || lower == 'sha-1' || lower == 'sha_1') { + result = sha1; + } else if (lower == 'sha256' || lower == 'sha-256' || lower == 'sha_256') { + result = sha256; + } else if (lower == 'sha384' || lower == 'sha-384' || lower == 'sha_384') { + result = sha384; + } else if (lower == 'sha512' || lower == 'sha-512' || lower == 'sha_512') { + result = sha512; + } else if (lower == 'md5' || lower == 'md-5' || lower == 'md_5') { + result = md5; + } else { + throw ArgumentError.value( + hashAlgorithm, 'hashAlgorithm', 'Invalid message digest algorithm'); + } + return result; + } + /// internal method List? digest(PdfStreamReader data, dynamic hashAlgorithm) { dynamic algorithm; @@ -598,6 +810,24 @@ class MessageDigestAlgorithms { input.close(); return output.events.single.bytes as List?; } + + /// internal method + Future?> digestAsync( + PdfStreamReader data, dynamic hashAlgorithm) async { + dynamic algorithm; + algorithm = hashAlgorithm is String + ? await getMessageDigestAsync(hashAlgorithm) + : hashAlgorithm; + final dynamic output = AccumulatorSink(); + final dynamic input = algorithm.startChunkedConversion(output); + int? count; + final List bytes = List.generate(8192, (int i) => 0); + while ((count = data.read(bytes, 0, bytes.length))! > 0) { + input.add(bytes.sublist(0, count)); + } + input.close(); + return output.events.single.bytes as List?; + } } class _SignaturePrivateKey { @@ -625,6 +855,15 @@ class _SignaturePrivateKey { return signer.generateSignature(); } + Future?> signAsync(List bytes) async { + final String signMode = '${_hashAlgorithm!}with${_encryptionAlgorithm!}'; + final _SignerUtilities util = _SignerUtilities(); + final ISigner signer = util.getSigner(signMode); + signer.initialize(true, _key); + signer.blockUpdate(bytes, 0, bytes.length); + return signer.generateSignature(); + } + String? getHashAlgorithm() { return _hashAlgorithm; } @@ -718,6 +957,31 @@ class _SignerUtilities { } return result; } + + Future getSignerAsync(String algorithm) async { + ISigner result; + final String lower = algorithm.toLowerCase(); + String? mechanism = algorithm; + bool isContinue = true; + _algms.forEach((String? key, String value) { + if (isContinue && key!.toLowerCase() == lower) { + mechanism = _algms[key]; + isContinue = false; + } + }); + if (mechanism == 'SHA-1withRSA') { + result = _RmdSigner(DigestAlgorithms.sha1); + } else if (mechanism == 'SHA-256withRSA') { + return _RmdSigner(DigestAlgorithms.sha256); + } else if (mechanism == 'SHA-384withRSA') { + return _RmdSigner(DigestAlgorithms.sha384); + } else if (mechanism == 'SHA-512withRSA') { + return _RmdSigner(DigestAlgorithms.sha512); + } else { + throw ArgumentError.value('Signer $algorithm not recognised.'); + } + return result; + } } class _PdfCmsSigner { @@ -808,6 +1072,50 @@ class _PdfCmsSigner { return DerSet(collection: attribute); } + //Implementation + Future getSequenceDataSetAsync( + List secondDigest, + List? ocsp, + List>? crlBytes, + CryptographicStandard? sigtype) async { + final Asn1EncodeCollection attribute = Asn1EncodeCollection(); + Asn1EncodeCollection v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.contentType)); + v.encodableObjects.add(DerSet( + array: [DerObjectID(_DigitalIdentifiers.pkcs7Data)])); + attribute.encodableObjects.add(DerSequence(collection: v)); + v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.messageDigest)); + v.encodableObjects.add(DerSet(array: [DerOctet(secondDigest)])); + attribute.encodableObjects.add(DerSequence(collection: v)); + if (sigtype == CryptographicStandard.cades) { + v = Asn1EncodeCollection(); + v.encodableObjects + .add(DerObjectID(_DigitalIdentifiers.aaSigningCertificateV2)); + final Asn1EncodeCollection aaV2 = Asn1EncodeCollection(); + final MessageDigestAlgorithms alg = MessageDigestAlgorithms(); + await alg + .getAllowedDigestsAsync(MessageDigestAlgorithms.secureHash256) + .then((String? sha256Oid) async { + if (sha256Oid != _digestAlgorithmOid) { + aaV2.encodableObjects + .add(Algorithms(DerObjectID(_digestAlgorithmOid))); + } + await alg.getMessageDigestAsync(hashAlgorithm!).then((dynamic value) { + aaV2.encodableObjects.add(DerOctet(value + .convert(_signCert!.c!.getEncoded(Asn1.der)) + .bytes as List)); + v.encodableObjects.add(DerSet(array: [ + DerSequence.fromObject( + DerSequence.fromObject(DerSequence(collection: aaV2))) + ])); + attribute.encodableObjects.add(DerSequence(collection: v)); + }); + }); + } + return DerSet(collection: attribute); + } + void setSignedData( List digest, List? rsaData, String? digestEncryptionAlgorithm) { _signedData = digest; @@ -826,6 +1134,24 @@ class _PdfCmsSigner { } } + Future setSignedDataAsync(List digest, List? rsaData, + String? digestEncryptionAlgorithm) async { + _signedData = digest; + _signedRsaData = rsaData; + if (digestEncryptionAlgorithm != null) { + if (digestEncryptionAlgorithm == 'RSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.rsa; + } else if (digestEncryptionAlgorithm == 'DSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.dsa; + } else if (digestEncryptionAlgorithm == 'ECDSA') { + _encryptionAlgorithmOid = _DigitalIdentifiers.ecdsa; + } else { + throw ArgumentError.value( + digestEncryptionAlgorithm, 'algorithm', 'Invalid entry'); + } + } + } + List? sign( List secondDigest, dynamic server, @@ -900,6 +1226,80 @@ class _PdfCmsSigner { return dout.stream; } + Future?> signAsync( + List secondDigest, + dynamic server, + List? timeStampResponse, + List? ocsp, + List>? crls, + CryptographicStandard? sigtype, + String? hashAlgorithm) async { + if (_signedData != null) { + _digest = _signedData; + if (_rsaData != null) { + _rsaData = _signedRsaData; + } + } + final Asn1EncodeCollection digestAlgorithms = Asn1EncodeCollection(); + final List keys = _digestOid.keys.toList(); + // ignore: avoid_function_literals_in_foreach_calls + keys.forEach((String? dal) { + final Asn1EncodeCollection algos = Asn1EncodeCollection(); + algos.encodableObjects.add(DerObjectID(dal)); + algos.encodableObjects.add(DerNull.value); + digestAlgorithms.encodableObjects.add(DerSequence(collection: algos)); + }); + Asn1EncodeCollection v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_DigitalIdentifiers.pkcs7Data)); + if (_rsaData != null) { + v.encodableObjects.add(DerTag(0, DerOctet(_rsaData!))); + } + final DerSequence contentinfo = DerSequence(collection: v); + + v = Asn1EncodeCollection(); + // ignore: avoid_function_literals_in_foreach_calls + _certificates.forEach((X509Certificate? xcert) { + v.encodableObjects.add( + Asn1Stream(PdfStreamReader(xcert!.c!.getEncoded(Asn1.der))) + .readAsn1()); + }); + final DerSet dercertificates = DerSet(collection: v); + final Asn1EncodeCollection signerinfo = Asn1EncodeCollection(); + signerinfo.encodableObjects + .add(DerInteger(bigIntToBytes(BigInt.from(_signerVersion)))); + v = Asn1EncodeCollection(); + v.encodableObjects + .add(getIssuer(_signCert!.c!.tbsCertificate!.getEncoded(Asn1.der))); + v.encodableObjects + .add(DerInteger(bigIntToBytes(_signCert!.c!.serialNumber!.value))); + signerinfo.encodableObjects.add(DerSequence(collection: v)); + v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_digestAlgorithmOid)); + v.encodableObjects.add(DerNull.value); + signerinfo.encodableObjects.add(DerSequence(collection: v)); + signerinfo.encodableObjects.add(DerTag( + 0, getSequenceDataSet(secondDigest, ocsp, crls, sigtype), false)); + v = Asn1EncodeCollection(); + v.encodableObjects.add(DerObjectID(_encryptionAlgorithmOid)); + v.encodableObjects.add(DerNull.value); + signerinfo.encodableObjects.add(DerSequence(collection: v)); + signerinfo.encodableObjects.add(DerOctet(_digest!)); + final Asn1EncodeCollection body = Asn1EncodeCollection(); + body.encodableObjects.add(DerInteger(bigIntToBytes(BigInt.from(_version)))); + body.encodableObjects.add(DerSet(collection: digestAlgorithms)); + body.encodableObjects.add(contentinfo); + body.encodableObjects.add(DerTag(0, dercertificates, false)); + body.encodableObjects + .add(DerSet(array: [DerSequence(collection: signerinfo)])); + final Asn1EncodeCollection whole = Asn1EncodeCollection(); + whole.encodableObjects + .add(DerObjectID(_DigitalIdentifiers.pkcs7SignedData)); + whole.encodableObjects.add(DerTag(0, DerSequence(collection: body))); + final Asn1DerStream dout = Asn1DerStream([]); + dout.writeObject(DerSequence(collection: whole)); + return dout.stream; + } + Asn1? getIssuer(List? data) { final Asn1Sequence seq = Asn1Stream(PdfStreamReader(data)).readAsn1()! as Asn1Sequence; @@ -1210,6 +1610,10 @@ class _NistObjectIds { typedef DocumentSavedHandler = void Function( Object sender, DocumentSavedArgs args); +/// internal type definition +typedef DocumentSavedHandlerAsync = Future Function( + Object sender, DocumentSavedArgs args); + /// internal class class DocumentSavedArgs { /// internal constructor diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart index 1920f1cb6..ce71c4536 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/pdf_encryptor.dart @@ -951,8 +951,9 @@ class PdfEncryptor { List.copyRange(hashProvided, 0, _ownerPasswordOut!, 0, 32); List.copyRange(ownerValidationSalt, 0, _ownerPasswordOut!, 32, 40); int userKeyLength = 48; - if (_userPasswordOut!.length < 48) + if (_userPasswordOut!.length < 48) { userKeyLength = _userPasswordOut!.length; + } final List mixedOwnerPassword = List.filled( ownerPassword.length + ownerValidationSalt.length + userKeyLength, 0, growable: true); @@ -1355,9 +1356,13 @@ class PdfEncryptor { PdfName(PdfDictionaryProperties.cryptFilter); } else { standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) ? PdfDictionaryProperties.aesv3 - : PdfDictionaryProperties.aesv2); + : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? 'V2' + : PdfDictionaryProperties.aesv2); } } if (!standardCryptFilter.containsKey(PdfDictionaryProperties.authEvent)) { @@ -1367,7 +1372,9 @@ class PdfEncryptor { : PdfDictionaryProperties.docOpen); } standardCryptFilter[PdfDictionaryProperties.length] = PdfNumber( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) ? _key256! : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) @@ -1388,9 +1395,13 @@ class PdfEncryptor { PdfName(PdfDictionaryProperties.cryptFilter); } else { standardCryptFilter[PdfDictionaryProperties.cfm] = PdfName( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) ? PdfDictionaryProperties.aesv3 - : PdfDictionaryProperties.aesv2); + : (encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) + ? 'V2' + : PdfDictionaryProperties.aesv2); } } if (!standardCryptFilter.containsKey(PdfDictionaryProperties.authEvent)) { @@ -1400,7 +1411,9 @@ class PdfEncryptor { : PdfDictionaryProperties.docOpen); } standardCryptFilter[PdfDictionaryProperties.length] = PdfNumber( - encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit + (encryptionAlgorithm == PdfEncryptionAlgorithm.aesx256Bit || + encryptionAlgorithm == + PdfEncryptionAlgorithm.aesx256BitRevision6) ? _key256! : ((encryptionAlgorithm == PdfEncryptionAlgorithm.aesx128Bit || encryptionAlgorithm == PdfEncryptionAlgorithm.rc4x128Bit) diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart index bdd9283a9..80620fffe 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/layouting/pdf_grid_layouter.dart @@ -25,7 +25,7 @@ import '../styles/style.dart'; /// internal class class PdfGridLayouter extends ElementLayouter { /// internal constructor - PdfGridLayouter(PdfGrid grid) : super(grid) { + PdfGridLayouter(PdfGrid super.grid) { _initialize(); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart index 151f51b0d..58b00f4af 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid.dart @@ -4805,8 +4805,7 @@ abstract class GridCellLayoutArgs { /// Arguments of BeginPageLayoutEvent. class PdfGridBeginPageLayoutArgs extends BeginPageLayoutArgs { //Constructor - PdfGridBeginPageLayoutArgs._(Rect bounds, PdfPage page, int? startRow) - : super(bounds, page) { + PdfGridBeginPageLayoutArgs._(super.bounds, super.page, int? startRow) { startRowIndex = startRow ?? 0; } @@ -4828,7 +4827,7 @@ class PdfGridBeginPageLayoutArgsHelper { /// Arguments of EndPageLayoutEvent. class PdfGridEndPageLayoutArgs extends EndPageLayoutArgs { //Constructor - PdfGridEndPageLayoutArgs._(PdfLayoutResult result) : super(result); + PdfGridEndPageLayoutArgs._(super.result); } // ignore: avoid_classes_with_only_static_members diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart index 6cad8c31f..4dca6daa6 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/styles/style.dart @@ -51,15 +51,10 @@ class PdfGridCellStyle extends PdfGridRowStyle { PdfStringFormat? format, PdfImage? backgroundImage, PdfPaddings? cellPadding, - PdfBrush? backgroundBrush, - PdfBrush? textBrush, - PdfPen? textPen, - PdfFont? font}) - : super( - backgroundBrush: backgroundBrush, - textBrush: textBrush, - textPen: textPen, - font: font) { + super.backgroundBrush, + super.textBrush, + super.textPen, + super.font}) { _initializeCellStyle(borders, format, backgroundImage, cellPadding); } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart index d414cc2e4..e4199ecb1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/pdf_list_layouter.dart @@ -30,7 +30,7 @@ import 'pdf_unordered_list.dart'; /// Layouts list. class PdfListLayouter extends ElementLayouter { /// Initializes a new instance of the [PdfListLayouter] class. - PdfListLayouter(PdfList element) : super(element); + PdfListLayouter(PdfList super.element); /// Current graphics for lay outing. PdfGraphics? graphics; @@ -919,8 +919,7 @@ class _ListInfo { /// Represents begin page layout event arguments. class ListBeginPageLayoutArgs extends BeginPageLayoutArgs { /// Initializes a new instance of the [ListBeginPageLayoutArgs] class. - ListBeginPageLayoutArgs._(Rect bounds, PdfPage page, this.list) - : super(bounds, page); + ListBeginPageLayoutArgs._(super.bounds, super.page, this.list); /// Gets the ended layout late PdfList list; @@ -939,8 +938,7 @@ class ListBeginPageLayoutArgsHelper { /// Represents begin page layout event arguments. class ListEndPageLayoutArgs extends EndPageLayoutArgs { /// Initializes a new instance of the [ListEndPageLayoutArgs] class. - ListEndPageLayoutArgs._internal(PdfLayoutResult result, this.list) - : super(result); + ListEndPageLayoutArgs._internal(super.result, this.list); /// Gets the ended layout PdfList list; diff --git a/packages/syncfusion_flutter_pdf/pubspec.yaml b/packages/syncfusion_flutter_pdf/pubspec.yaml index f20c85771..6e3a281b9 100644 --- a/packages/syncfusion_flutter_pdf/pubspec.yaml +++ b/packages/syncfusion_flutter_pdf/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_pdf description: The Flutter PDF is a library written natively in Dart for creating, reading, editing, and securing PDF files in Android, iOS, and web platforms. -version: 23.1.44 +version: 24.2.8 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdf environment: @@ -9,8 +9,8 @@ environment: dependencies: flutter: sdk: flutter - intl: ^0.18.0 - xml: ">=5.1.0 <7.0.0" + intl: '>=0.18.1 <0.20.0' + xml: ">=6.5.0 <7.0.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core crypto: ">=3.0.0 <4.0.0" diff --git a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md index 6e3bd2c98..83937e855 100644 --- a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md @@ -1,5 +1,64 @@ ## Unreleased +**Features** + +* Provided support for loading the PDF document on a specified page. +* Provided support for active viewport rendering. That is, at a higher zoom level, this feature renders only the part of the PDF document that is visible on the screen, ignoring the parts that are outside the viewport. +* Provided support to undo and redo the data filled in PDF forms. + +**General** + +* Provided the Material 3 theme support. + +## [24.2.9] - 03/05/2024 + +**General** + +* Upgraded the `js` package to the latest version 0.7.1 in the `syncfusion_pdfviewer_web` package. + +## [24.2.8] - 02/27/2024 + +**Bugs** + +* Now the page will be centered while zooming out in landscape orientation with pinch gestures in Single Page layout mode. + +## [24.2.7] - 02/20/2024 + +**Features** + +* Provided support to check and uncheck the grouped checkbox form fields that behave like a radio button in the PDF document. + +## [24.2.5] - 02/13/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will not crash unexpectedly when closing the application before the PDF document is loaded on iOS and macOS platforms. + +## [24.1.47] - 01/23/2024 + +**Bugs** + +* Now, when viewing a single-page document in a continuous page layout, the user can fully zoom out of the document with a pinch gesture in the `SfPdfViewer` widget. + +## [24.1.46] - 01/17/2024 + +**Bugs** + +* Now, the application will not crash when switching the PDF document in the `SfPdfViewer` widget on the Windows platform. + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + +## [24.1.45] - 01/09/2024 + +**Bugs** + +* Now, the `SfPdfViewer` will render the PDF pages considering the crop box value in iOS. +* Now, when using German locale, the 'Open' label in the hyperlink and password dialogs will be translated properly. + +## [24.1.41] - 18/12/2023 + **Bugs** * Now, the `SfPdfViewer` will be properly deployed without namespace errors when built with a Gradle version greater than 8.x. diff --git a/packages/syncfusion_flutter_pdfviewer/README.md b/packages/syncfusion_flutter_pdfviewer/README.md index e4d5abd51..2fdb015f3 100644 --- a/packages/syncfusion_flutter_pdfviewer/README.md +++ b/packages/syncfusion_flutter_pdfviewer/README.md @@ -12,12 +12,12 @@ The Flutter PDF Viewer plugin lets you view PDF documents seamlessly and efficie - [Useful links](#other-useful-links) - [Installation](#installation) - [Getting started](#getting-started) - - [Add PDF Viewer to the widget tree](#add-pdf-viewer-to-the-widget-tree) - - [Load PDF document from the Asset](#load-pdf-document-from-the-asset) - - [Load PDF document from the Network](#load-pdf-document-from-the-network) - - [Load PDF document from the File](#load-pdf-document-from-the-file) - - [Load PDF document from the Memory](#load-pdf-document-from-the-memory) - - [Load encrypted PDF document](#load-encrypted-pdf-document) + - [Add PDF Viewer to the widget tree](#add-pdf-viewer-to-the-widget-tree) + - [Load PDF document from the Asset](#load-pdf-document-from-the-asset) + - [Load PDF document from the Network](#load-pdf-document-from-the-network) + - [Load PDF document from the File](#load-pdf-document-from-the-file) + - [Load PDF document from the Memory](#load-pdf-document-from-the-memory) + - [Load encrypted PDF document](#load-encrypted-pdf-document) - [Support and feedback](#support-and-feedback) - [About Syncfusion](#about-syncfusion) @@ -31,19 +31,19 @@ The Flutter PDF Viewer plugin lets you view PDF documents seamlessly and efficie * **Page navigation** - Navigate to the desired pages instantly. - ![syncfusion_flutter_pdfviewer_page_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/pagination-dialog.png) + ![syncfusion_flutter_pdfviewer_page_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/pagination-dialog.png) * **Text selection** - Select text presented in a PDF document. - ![syncfusion_flutter_pdfviewer_text_selection](https://cdn.syncfusion.com/content/images/PDFViewer/text-selection.png) + ![syncfusion_flutter_pdfviewer_text_selection](https://cdn.syncfusion.com/content/images/PDFViewer/text-selection.png) * **Text search** - Search for text and navigate to all its occurrences in a PDF document instantly. - ![syncfusion_flutter_pdfviewer_text_search](https://cdn.syncfusion.com/content/images/PDFViewer/text-search.png) + ![syncfusion_flutter_pdfviewer_text_search](https://cdn.syncfusion.com/content/images/PDFViewer/text-search.png) * **Bookmark navigation** - Bookmarks saved in the document are loaded and made ready for easy navigation. This feature helps in navigation within the PDF document of the topics bookmarked already. - ![syncfusion_flutter_pdfviewer_bookmark_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation.png) + ![syncfusion_flutter_pdfviewer_bookmark_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation.png) * **Document link annotation navigation** - Navigate to the desired topic or position by tapping the document link annotation of the topics in the table of contents in a PDF document. @@ -51,27 +51,27 @@ The Flutter PDF Viewer plugin lets you view PDF documents seamlessly and efficie * **Text markup annotations** - Add, remove, and modify text markup annotations in PDF files. The available text markups are highlight, underline, strikethrough and squiggly. This feature will help mark important passages, emphasize specific words or phrases, indicate that certain content should be removed or indicate that text contains possible errors. - ![syncfusion_flutter_pdfviewer_text_markup_highlight](https://cdn.syncfusion.com/content/images/PDFViewer/highlight.png) + ![syncfusion_flutter_pdfviewer_text_markup_highlight](https://cdn.syncfusion.com/content/images/PDFViewer/highlight.png) - ![syncfusion_flutter_pdfviewer_text_markup_underline](https://cdn.syncfusion.com/content/images/PDFViewer/underline.png) + ![syncfusion_flutter_pdfviewer_text_markup_underline](https://cdn.syncfusion.com/content/images/PDFViewer/underline.png) - ![syncfusion_flutter_pdfviewer_text_markup_strikethrough](https://cdn.syncfusion.com/content/images/PDFViewer/strikethrough.png) + ![syncfusion_flutter_pdfviewer_text_markup_strikethrough](https://cdn.syncfusion.com/content/images/PDFViewer/strikethrough.png) - ![syncfusion_flutter_pdfviewer_text_markup_squiggly](https://cdn.syncfusion.com/content/images/PDFViewer/squiggly.png) + ![syncfusion_flutter_pdfviewer_text_markup_squiggly](https://cdn.syncfusion.com/content/images/PDFViewer/squiggly.png) * **Form filling** - Fill, edit, flatten, save, export, and import AcroForm field data in a PDF document. - ![syncfusion_flutter_pdfviewer_form_filling](https://cdn.syncfusion.com/content/images/PDFViewer/form-filling.gif) + ![syncfusion_flutter_pdfviewer_form_filling](https://cdn.syncfusion.com/content/images/PDFViewer/form-filling.gif) * **Right to Left (RTL)** - Change the user interface and functionalities such as text search and text copying to accommodate RTL languages such as Hebrew and Arabic. * **Themes** - Easily switch between the light and dark theme. - ![syncfusion_flutter_pdfviewer_theme](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation-dark.png) + ![syncfusion_flutter_pdfviewer_theme](https://cdn.syncfusion.com/content/images/PDFViewer/bookmark-navigation-dark.png) * **Localization** - All static text within the PDF Viewer can be localized to any supported language. - ![syncfusion_flutter_pdfviewer_localization](https://cdn.syncfusion.com/content/images/PDFViewer/localization.png) + ![syncfusion_flutter_pdfviewer_localization](https://cdn.syncfusion.com/content/images/PDFViewer/localization.png) ## Get the demo application @@ -208,6 +208,6 @@ Widget build(BuildContext context) { ## About Syncfusion -Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. +Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 20,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. Today we provide 1,000+ controls and frameworks for web ([ASP.NET Core](https://www.syncfusion.com/aspnet-core-ui-controls), [ASP.NET MVC](https://www.syncfusion.com/aspnet-mvc-ui-controls), [ASP.NET WebForms](https://www.syncfusion.com/jquery/aspnet-web-forms-ui-controls), [JavaScript](https://www.syncfusion.com/javascript-ui-controls), [Angular](https://www.syncfusion.com/angular-ui-components), [React](https://www.syncfusion.com/react-ui-components), [Vue](https://www.syncfusion.com/vue-ui-components), [Flutter](https://www.syncfusion.com/flutter-widgets), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), [Flutter](https://www.syncfusion.com/flutter-widgets), [UWP](https://www.syncfusion.com/uwp-ui-controls), and [JavaScript](https://www.syncfusion.com/javascript-ui-controls)), and desktop development ([Flutter](https://www.syncfusion.com/flutter-widgets), [WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls), [.NET MAUI](https://www.syncfusion.com/maui-controls), and [WinUI](https://www.syncfusion.com/winui-controls)). We provide ready-to deploy enterprise software for dashboards, reports, data integration, and big data processing. Many customers have saved millions in licensing fees by deploying our software. \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java b/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java index f539d43b6..7add7a276 100644 --- a/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java +++ b/packages/syncfusion_flutter_pdfviewer/android/src/main/java/com/syncfusion/flutter/pdfviewer/SyncfusionFlutterPdfViewerPlugin.java @@ -3,6 +3,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.pdf.PdfRenderer; import android.os.Build; @@ -85,6 +86,15 @@ public void onMethodCall(@NonNull final MethodCall call, @NonNull final Result r Double.parseDouble(Objects.requireNonNull(call.argument("scale")).toString()), (String) call.argument("documentID")); break; + case "getTileImage": + getTileImage(Integer.parseInt(Objects.requireNonNull(call.argument("pageNumber")).toString()), + Double.parseDouble(Objects.requireNonNull(call.argument("scale")).toString()), + Double.parseDouble(Objects.requireNonNull(call.argument("x")).toString()), + Double.parseDouble(Objects.requireNonNull(call.argument("y")).toString()), + Double.parseDouble(Objects.requireNonNull(call.argument("width")).toString()), + Double.parseDouble(Objects.requireNonNull(call.argument("height")).toString()), + (String) call.argument("documentID")); + break; case "initializePdfRenderer": result.success(initializePdfRenderer((byte[]) call.argument("documentBytes"), (String) call.argument("documentID"))); @@ -222,6 +232,17 @@ void getImage(int pageIndex, double scale, String documentID) { resultPdf.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); } } + + void getTileImage(int pageNumber, double currentScale, double x, double y, double width, double height, String documentID) { + try { + ExecutorService executor = Executors.newCachedThreadPool(); + Runnable bitmapRunnable = new TileImageRunnable(Objects.requireNonNull(documentRepo.get(documentID)).renderer, + resultPdf, pageNumber, currentScale, x, y, width, height); + executor.submit(bitmapRunnable); + } catch (Exception e) { + resultPdf.error(e.getMessage(), e.getLocalizedMessage(), e.getMessage()); + } + } @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) boolean closeDocument(String documentID) { @@ -306,4 +327,70 @@ public void run() { } }); } +} + +class TileImageRunnable implements Runnable { + private byte[] imageBytes = null; + final private PdfRenderer renderer; + final private Result resultPdf; + final private int pageIndex; + private double scale; + + private double tileWidth; + private double tileHeight; + + private double tileX; + private double tileY; + + private PdfRenderer.Page page; + + TileImageRunnable(PdfRenderer renderer, Result resultPdf, int pageIndex, double currentScale, double x, + double y, double width, double height) { + + this.resultPdf = resultPdf; + this.renderer = renderer; + this.pageIndex = pageIndex; + this.scale = currentScale; + + this.tileWidth = width; + this.tileHeight = height; + this.tileX = x; + this.tileY = y; + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void dispose() { + imageBytes = null; + if (page != null) { + page.close(); + page = null; + } + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public void run() { + page = renderer.openPage(pageIndex - 1); + + final Bitmap bitmap = Bitmap.createBitmap((int)tileWidth, (int)tileHeight, Bitmap.Config.ARGB_8888); + bitmap.eraseColor(Color.WHITE); + Matrix matrix = new Matrix(); + matrix.postTranslate((float)-tileX, (float)-tileY); + matrix.postScale((float)(scale), (float)(scale)); + final Rect rect = new Rect(0, 0, (int)tileWidth, (int)tileHeight); + page.render(bitmap, rect, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); + page.close(); + page = null; + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream); + imageBytes = outStream.toByteArray(); + synchronized (this) { + notifyAll(); + } + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + resultPdf.success(imageBytes); + } + }); + } } \ No newline at end of file diff --git a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart index 51f2d33d4..30293062e 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart @@ -4,9 +4,6 @@ import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; void main() { runApp(MaterialApp( title: 'Syncfusion PDF Viewer Demo', - theme: ThemeData( - useMaterial3: false, - ), home: HomePage(), )); } diff --git a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift index 76132daa4..228c730f7 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift @@ -9,13 +9,6 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { var documentRepo = [String: CGPDFDocument?]() let dispatcher = DispatchQueue(label: "syncfusion_flutter_pdfviewer") - - // Point to pixel ratio calculation - // 1 inch = 96 pixels - // 1 inch = 72 points - // 72 points = 96 pixels - // 1 point = 96/72 pixels - let pointToPixelRatio = 96.0 / 72.0 // Registers the SyncfusionFlutterPdfViewerPlugin public static func register(with registrar: FlutterPluginRegistrar) { @@ -35,6 +28,12 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { self.getImage(call:call,result:result) } } + else if(call.method == "getTileImage") + { + dispatcher.async { + self.getTileImage(call:call,result:result) + } + } else if(call.method == "getPagesWidth") { getPagesWidth(call:call,result:result) @@ -78,11 +77,11 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { guard let argument = call.arguments else {return} let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let document = self.documentRepo[documentID] else {return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesWidth = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) + let page = document!.page(at: Int(index)) var pageRect = page!.getBoxRect(.cropBox) if(page!.rotationAngle > 0) { @@ -99,11 +98,11 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { guard let argument = call.arguments else {return} let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let document = self.documentRepo[documentID] else { return } + let pageCount = NSNumber(value: document!.numberOfPages) var pagesHeight = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) + let page = document!.page(at: Int(index)) var pageRect = page!.getBoxRect(.cropBox) if(page!.rotationAngle > 0) { @@ -126,16 +125,19 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { scale = 2 } - scale = scale * CGFloat(pointToPixelRatio) let documentID = args!["documentID"] as! String - result(getImageForPlugin(index: index!,scale: scale,documentID: documentID)) + guard let image = getImageForPlugin(index: index!,scale: scale,documentID: documentID) else { + result(FlutterStandardTypedData()) + return + } + result(image) } - private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData + private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData? { - let document = self.documentRepo[documentID]!! - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.cropBox) + guard let document = self.documentRepo[documentID] else {return nil} + let page = document!.page(at: Int(index)) + let pageRect = page!.getBoxRect(.cropBox) let imageRect = CGRect(x: 0,y: 0,width: pageRect.size.width*CGFloat(scale),height: pageRect.size.height*CGFloat(scale)) if #available(iOS 10.0, *) { let format = UIGraphicsImageRendererFormat() @@ -147,8 +149,7 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { } let renderer = UIGraphicsImageRenderer(size: imageRect.size,format: format) let img = renderer.image { ctx in - let cropBox = page!.getBoxRect(.cropBox) - let transform = page!.getDrawingTransform(.cropBox, rect: cropBox, rotate: 0, preserveAspectRatio: true) + let transform = page!.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: pageRect.size), rotate: 0, preserveAspectRatio: true) ctx.cgContext.translateBy(x: 0.0, y: imageRect.size.height) ctx.cgContext.scaleBy(x: CGFloat(scale), y: -CGFloat(scale)) ctx.cgContext.concatenate(transform) @@ -159,4 +160,68 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { } return FlutterStandardTypedData() } -} \ No newline at end of file + + // Gets the pdf page image from the specified page + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + let args = argument as? [String: Any] + let pageNumber = args!["pageNumber"] as? Int + let scale = CGFloat(args!["scale"] as! Double) + let width = args!["width"] as! Double + let height = args!["height"] as! Double + let x = args!["x"] as! Double + let y = args!["y"] as! Double + + let documentID = args!["documentID"] as! String + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber!, scale: scale, + width: width, height: height, x: x, y: y, documentID: documentID) else { + result(FlutterStandardTypedData()) + return + } + result(tileImage) + } + + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else{return nil} + let page = document!.page(at: Int(pageNumber)) + let pageRect = page!.getBoxRect(.cropBox) + + var pageWidth = pageRect.width + var pageHeight = pageRect.height + + if(page!.rotationAngle == 90 || page!.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let imageRect = CGRect(x: 0,y: 0, width: width, height: height) + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + if #available(iOS 10.0, *) { + let format = UIGraphicsImageRendererFormat() + format.scale = 1 + if #available(iOS 12.0, *) { + format.preferredRange = .standard + } else { + format.prefersExtendedRange = false + } + let renderer = UIGraphicsImageRenderer(size: imageRect.size,format: format) + + let img = renderer.image { ctx in + let transform = page!.getDrawingTransform(.cropBox, rect: bounds, rotate: 0, preserveAspectRatio: true) + ctx.cgContext.translateBy(x: 0.0, y: pageHeight * scale) + ctx.cgContext.scaleBy(x: scale, y: -scale) + ctx.cgContext.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + ctx.cgContext.fill(bounds) + ctx.cgContext.concatenate(transform) + ctx.cgContext.drawPDFPage(page!) + ctx.cgContext.endPage() + } + return FlutterStandardTypedData(bytes: img.pngData()!) + } + return FlutterStandardTypedData() + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart index 015759d5d..deae56f8f 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart @@ -2,6 +2,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; /// Width of the back icon in the bookmark. const double _kPdfBackIconWidth = 24.0; @@ -106,11 +107,16 @@ class BookmarkItem extends StatefulWidget { class _BookmarkItemState extends State { late Color _color; SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -120,11 +126,13 @@ class _BookmarkItemState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; super.dispose(); } void _handleBackToParent() { _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -133,6 +141,7 @@ class _BookmarkItemState extends State { void _handleExpandBookmarkList() { _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -153,6 +162,7 @@ class _BookmarkItemState extends State { _color = const Color(0xFF000000).withOpacity(0.08); } else { _color = _pdfViewerThemeData!.bookmarkViewStyle?.selectionColor! ?? + _effectiveThemeData!.bookmarkViewStyle?.selectionColor! ?? ((Theme.of(context).colorScheme.brightness == Brightness.light) ? const Color.fromRGBO(0, 0, 0, 0.08) : const Color.fromRGBO(255, 255, 255, 0.12)); @@ -167,6 +177,7 @@ class _BookmarkItemState extends State { void _handleCancelSelectionColor() { setState(() { _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); @@ -190,6 +201,8 @@ class _BookmarkItemState extends State { bottom: BorderSide( color: _pdfViewerThemeData! .bookmarkViewStyle?.titleSeparatorColor ?? + _effectiveThemeData! + .bookmarkViewStyle?.titleSeparatorColor ?? ((Theme.of(context).colorScheme.brightness == Brightness.light) ? const Color.fromRGBO(0, 0, 0, 0.16) @@ -212,12 +225,13 @@ class _BookmarkItemState extends State { child: Icon( Icons.arrow_back, size: _kPdfBackIconSize, - color: - _pdfViewerThemeData!.bookmarkViewStyle?.backIconColor ?? - Theme.of(context) - .colorScheme - .onSurface - .withOpacity(0.54), + color: _pdfViewerThemeData! + .bookmarkViewStyle?.backIconColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backIconColor ?? + Theme.of(context) + .colorScheme + .onSurface + .withOpacity(0.54), semanticLabel: 'Previous level bookmark', ), ), @@ -257,6 +271,8 @@ class _BookmarkItemState extends State { size: _kPdfExpandIconSize, color: _pdfViewerThemeData! .bookmarkViewStyle?.navigationIconColor ?? + _effectiveThemeData! + .bookmarkViewStyle?.navigationIconColor ?? Theme.of(context) .colorScheme .onSurface @@ -275,12 +291,15 @@ class _BookmarkItemState extends State { cursor: SystemMouseCursors.click, onEnter: (PointerEnterEvent details) { setState(() { - _color = const Color(0xFF000000).withOpacity(0.04); + _color = Theme.of(context).useMaterial3 + ? Theme.of(context).colorScheme.onSurface.withOpacity(0.08) + : const Color(0xFF000000).withOpacity(0.04); }); }, onExit: (PointerExitEvent details) { setState(() { _color = _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart index 5308a4972..b18c869cf 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_toolbar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../theme/theme.dart'; /// Height of the bookmark header bar. const double _kPdfHeaderBarHeight = 53.0; @@ -49,11 +50,15 @@ class BookmarkToolbar extends StatefulWidget { /// State for [BookmarkToolbar] class _BookmarkToolbarState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -61,6 +66,7 @@ class _BookmarkToolbarState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -90,6 +96,7 @@ class _BookmarkToolbarState extends State { margin: const EdgeInsets.only(bottom: 3), decoration: BoxDecoration( color: _pdfViewerThemeData!.bookmarkViewStyle?.headerBarColor ?? + _effectiveThemeData!.bookmarkViewStyle?.headerBarColor ?? ((Theme.of(context).colorScheme.brightness == Brightness.light) ? const Color(0xFFFAFAFA) : const Color(0xFF424242)), @@ -114,7 +121,9 @@ class _BookmarkToolbarState extends State { : Colors.white.withOpacity(0.87), ) .merge(_pdfViewerThemeData! - .bookmarkViewStyle?.headerTextStyle), + .bookmarkViewStyle?.headerTextStyle ?? + _effectiveThemeData! + .bookmarkViewStyle?.headerTextStyle), semanticsLabel: '', ), ), @@ -133,6 +142,7 @@ class _BookmarkToolbarState extends State { size: _kPdfCloseIconSize, color: _pdfViewerThemeData! .bookmarkViewStyle?.closeIconColor ?? + _effectiveThemeData!.bookmarkViewStyle?.closeIconColor ?? Theme.of(context).colorScheme.onSurface.withOpacity(0.54), semanticLabel: 'Close Bookmark', ), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart index 031d88872..12763372f 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart @@ -6,6 +6,7 @@ import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; import 'bookmark_item.dart'; import 'bookmark_toolbar.dart'; @@ -57,6 +58,7 @@ class BookmarkViewControllerState extends State { int? _listCount; LocalHistoryEntry? _historyEntry; SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; /// If true, bookmark view is opened. @@ -65,6 +67,9 @@ class BookmarkViewControllerState extends State { @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -74,6 +79,7 @@ class BookmarkViewControllerState extends State { _bookmarkList!.clear(); _bookmarkList = null; _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -230,6 +236,7 @@ class BookmarkViewControllerState extends State { alignment: _isTablet ? Alignment.topRight : Alignment.center, child: Container( color: _pdfViewerThemeData!.bookmarkViewStyle?.backgroundColor ?? + _effectiveThemeData!.bookmarkViewStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF212121)), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart index 669f4f883..dcfdee3fa 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/change_tracker/change_command.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart' show Color; +import '../../pdfviewer.dart'; import '../annotation/annotation.dart'; +import '../form_fields/pdf_form_field.dart'; /// The base class for all annotation change commands. abstract class ChangeCommand { @@ -118,3 +120,50 @@ class ClearAnnotationsTracker extends ChangeCommand { } } } + +/// Represents a change in the value of the form field. +class FormFieldValueChangeTracker extends ChangeCommand { + /// Creates a new instance of [FormFieldValueChangeTracker]. + FormFieldValueChangeTracker( + {required this.records, required this.onUndoOrRedo}); + + /// The records of the changes made in the form fields + final List records; + + /// Occurs when undoing or redoing the change in the form fields. + final void Function(PdfFormField, Object?, bool) onUndoOrRedo; + + /// Undoes the change. + @override + void undo() { + for (final FormFieldValueChangeRecord record in records) { + onUndoOrRedo(record.formField, record.oldValue, true); + } + } + + /// Redoes the change. + @override + void redo() { + for (final FormFieldValueChangeRecord record in records) { + onUndoOrRedo(record.formField, record.newValue, true); + } + } +} + +/// Represents a change in the value of the form field. +class FormFieldValueChangeRecord { + /// Creates a new instance of [FormFieldValueChangeRecord]. + FormFieldValueChangeRecord( + {required this.formField, + required this.oldValue, + required this.newValue}); + + /// The form field whose value is changed. + final PdfFormField formField; + + /// The old value of the form field. + final Object? oldValue; + + /// The new value of the form field. + final Object? newValue; +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart index fffee2adc..8cdb56f8a 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart @@ -19,6 +19,7 @@ class PdfViewerPlugin { Map>? _renderedPages = >{}; Map? _pageScale = {}; CancelableOperation? _nativeImage; + CancelableOperation? _tileImage; /// Initialize the PDF renderer. Future initializePdfRenderer(Uint8List documentBytes) async { @@ -102,6 +103,7 @@ class PdfViewerPlugin { bool isZoomChanged, int currentPageNumber, bool canRenderImage) async { + currentScale = currentScale > 1.75 ? 1.75 : currentScale; imageCache.clear(); if (!canRenderImage) { _nativeImage?.cancel(); @@ -147,6 +149,20 @@ class PdfViewerPlugin { return Future>?>.value(_renderedPages); } + /// Returns the tile image of the specified page. + Future? getTileImage(int pageNumber, double scale, double x, + double y, double width, double height) async { + _tileImage = CancelableOperation.fromFuture(PdfViewerPlatform + .instance + .getTileImage(pageNumber, scale, x, y, width, height, _documentID!)); + final Future imageFuture = _tileImage!.value; + final Uint8List? image = await imageFuture; + if (image != null) { + return image; + } + return null; + } + /// Dispose the rendered pages Future closeDocument() async { imageCache.clear(); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart index c0f389515..613878ca1 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_page_view.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; + import '../../pdfviewer.dart'; import '../annotation/annotation_container.dart'; import '../common/mobile_helper.dart' @@ -16,6 +17,7 @@ import '../form_fields/pdf_list_box.dart'; import '../form_fields/pdf_radio_button.dart'; import '../form_fields/pdf_signature.dart'; import '../form_fields/pdf_text_box.dart'; +import '../theme/theme.dart'; import 'pdf_scrollable.dart'; import 'pdfviewer_canvas.dart'; import 'single_page_view.dart'; @@ -66,6 +68,8 @@ class PdfPageView extends StatefulWidget { this.canShowPageLoadingIndicator, this.canShowSignaturePadDialog, this.onTap, + this.viewportBounds, + this.tileImages, this.formFields, this.annotations, this.selectedAnnotation, @@ -208,6 +212,12 @@ class PdfPageView extends StatefulWidget { /// Called when the user taps on the page. final Function(Offset, int) onTap; + /// Tile bounds collection. + final Map viewportBounds; + + /// Tile image collection. + final Map tileImages; + @override State createState() { return PdfPageViewState(); @@ -217,6 +227,7 @@ class PdfPageView extends StatefulWidget { /// State for [PdfPageView] class PdfPageViewState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; final GlobalKey _canvasKey = GlobalKey(); int _lastTap = DateTime.now().millisecondsSinceEpoch; int _consecutiveTaps = 1; @@ -258,6 +269,9 @@ class PdfPageViewState extends State { @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); super.didChangeDependencies(); } @@ -267,6 +281,7 @@ class PdfPageViewState extends State { PaintingBinding.instance.imageCache.clearLiveImages(); focusNode.dispose(); _pdfViewerThemeData = null; + _effectiveThemeData = null; _formFields.clear(); super.dispose(); } @@ -293,9 +308,27 @@ class PdfPageViewState extends State { ? pageSpacing : 0.0; if (widget.imageStream != null) { - _buildFormFields(); final PdfPageRotateAngle rotatedAngle = widget.pdfDocument!.pages[widget.pageIndex].rotation; + int quarterTurns = 0; + if (rotatedAngle == PdfPageRotateAngle.rotateAngle90) { + quarterTurns = 1; + } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle180) { + quarterTurns = 2; + } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle270) { + quarterTurns = 3; + } + final bool isRotatedTo90or270 = + rotatedAngle == PdfPageRotateAngle.rotateAngle90 || + rotatedAngle == PdfPageRotateAngle.rotateAngle270; + final Size originalPageSize = + widget.pdfDocument!.pages[widget.pageIndex].size; + _heightPercentage = (isRotatedTo90or270 + ? originalPageSize.width + : originalPageSize.height) / + widget.pdfPages[widget.pageIndex + 1]!.pageSize.height; + + _buildFormFields(); final Widget image = Image.memory( widget.imageStream!, width: widget.width, @@ -311,10 +344,37 @@ class PdfPageViewState extends State { alignment: Alignment.topCenter, child: widget.scrollDirection == PdfScrollDirection.vertical ? Column(children: [ - image, + Stack( + children: [ + image, + if (widget.pdfViewerController.zoomLevel > 1.75 && + widget.viewportBounds.isNotEmpty && + widget.tileImages.isNotEmpty && + widget.viewportBounds[widget.pageIndex + 1] != null && + widget.tileImages[widget.pageIndex + 1] != null) + Positioned( + top: widget.viewportBounds[widget.pageIndex + 1]!.top / + _heightPercentage, + left: + widget.viewportBounds[widget.pageIndex + 1]!.left / + _heightPercentage, + width: + widget.viewportBounds[widget.pageIndex + 1]!.width / + _heightPercentage, + height: widget + .viewportBounds[widget.pageIndex + 1]!.height / + _heightPercentage, + child: Image.memory( + widget.tileImages[widget.pageIndex + 1]!, + fit: BoxFit.fill, + ), + ), + ], + ), Container( height: widget.isSinglePageView ? 0.0 : pageSpacing, color: _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) @@ -322,10 +382,37 @@ class PdfPageViewState extends State { ) ]) : Row(children: [ - image, + Stack( + children: [ + image, + if (widget.pdfViewerController.zoomLevel > 1.75 && + widget.viewportBounds.isNotEmpty && + widget.tileImages.isNotEmpty && + widget.viewportBounds[widget.pageIndex + 1] != null && + widget.tileImages[widget.pageIndex + 1] != null) + Positioned( + top: widget.viewportBounds[widget.pageIndex + 1]!.top / + _heightPercentage, + left: + widget.viewportBounds[widget.pageIndex + 1]!.left / + _heightPercentage, + width: + widget.viewportBounds[widget.pageIndex + 1]!.width / + _heightPercentage, + height: widget + .viewportBounds[widget.pageIndex + 1]!.height / + _heightPercentage, + child: Image.memory( + widget.tileImages[widget.pageIndex + 1]!, + fit: BoxFit.fill, + ), + ), + ], + ), Container( width: widget.isSinglePageView ? 0.0 : pageSpacing, color: _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) @@ -333,23 +420,6 @@ class PdfPageViewState extends State { ) ]), ); - int quarterTurns = 0; - if (rotatedAngle == PdfPageRotateAngle.rotateAngle90) { - quarterTurns = 1; - } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle180) { - quarterTurns = 2; - } else if (rotatedAngle == PdfPageRotateAngle.rotateAngle270) { - quarterTurns = 3; - } - final bool isRotatedTo90or270 = - rotatedAngle == PdfPageRotateAngle.rotateAngle90 || - rotatedAngle == PdfPageRotateAngle.rotateAngle270; - final Size originalPageSize = - widget.pdfDocument!.pages[widget.pageIndex].size; - _heightPercentage = (isRotatedTo90or270 - ? originalPageSize.width - : originalPageSize.height) / - widget.pdfPages[widget.pageIndex + 1]!.pageSize.height; final PdfAnnotationMode annotationMode = widget.pdfViewerController.annotationMode; @@ -467,11 +537,12 @@ class PdfPageViewState extends State { } } }, - child: RawKeyboardListener( + child: KeyboardListener( focusNode: focusNode, - onKey: (RawKeyEvent event) { - final bool isPrimaryKeyPressed = - kIsMacOS ? event.isMetaPressed : event.isControlPressed; + onKeyEvent: (KeyEvent event) { + final bool isPrimaryKeyPressed = kIsMacOS + ? HardwareKeyboard.instance.isMetaPressed + : HardwareKeyboard.instance.isControlPressed; if ((canvasRenderBox! .getSelectionDetails() .mouseSelectionEnabled || @@ -492,7 +563,7 @@ class PdfPageViewState extends State { } if (isPrimaryKeyPressed && event.logicalKey == LogicalKeyboardKey.minus) { - if (event is RawKeyDownEvent) { + if (event is KeyDownEvent) { double zoomLevel = widget.pdfViewerController.zoomLevel; if (zoomLevel > 1) { zoomLevel = zoomLevel - 0.5; @@ -502,13 +573,13 @@ class PdfPageViewState extends State { } if (isPrimaryKeyPressed && event.logicalKey == LogicalKeyboardKey.equal) { - if (event is RawKeyDownEvent) { + if (event is KeyDownEvent) { double zoomLevel = widget.pdfViewerController.zoomLevel; zoomLevel = zoomLevel + 0.5; widget.pdfViewerController.zoomLevel = zoomLevel; } } - if (event is RawKeyDownEvent) { + if (event is KeyDownEvent) { if (event.logicalKey == LogicalKeyboardKey.home || (kIsMacOS && event.logicalKey == LogicalKeyboardKey.fn && @@ -610,9 +681,9 @@ class PdfPageViewState extends State { _onPageTapped(details.localPosition); }, child: widget.isAndroidTV - ? RawKeyboardListener( + ? KeyboardListener( focusNode: focusNode, - onKey: (RawKeyEvent event) { + onKeyEvent: (KeyEvent event) { if (event.runtimeType.toString() == 'RawKeyDownEvent') { if (event.logicalKey == @@ -685,6 +756,7 @@ class PdfPageViewState extends State { final BorderSide borderSide = BorderSide( width: widget.isSinglePageView ? pageSpacing / 2 : pageSpacing, color: _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) : const Color(0xFF303030))); @@ -705,10 +777,16 @@ class PdfPageViewState extends State { child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( _pdfViewerThemeData!.progressBarColor ?? + _effectiveThemeData!.progressBarColor ?? (Theme.of(context).colorScheme.primary)), - backgroundColor: _pdfViewerThemeData!.progressBarColor == null - ? (Theme.of(context).colorScheme.primary.withOpacity(0.2)) - : _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2), + backgroundColor: _pdfViewerThemeData!.progressBarColor != null + ? _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2) + : _effectiveThemeData!.progressBarColor != null + ? _effectiveThemeData!.progressBarColor!.withOpacity(0.2) + : (Theme.of(context) + .colorScheme + .primary + .withOpacity(0.2)), ), ), ), @@ -717,6 +795,13 @@ class PdfPageViewState extends State { } } + /// Method to rebuild the widget + void rebuild() { + if (mounted) { + setState(() {}); + } + } + void _addTextMarkupAnnotation(String type) { final List? selectedLines = canvasRenderBox!.getSelectedTextLines(); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart index bcee5f6aa..03f6b3233 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart @@ -19,6 +19,7 @@ typedef OffsetChangedCallback = void Function(Offset offset); class PdfScrollable extends StatefulWidget { /// Constructor for PdfScrollable. const PdfScrollable( + this.transformationController, this.canShowPaginationDialog, this.canShowScrollStatus, this.canShowScrollHead, @@ -41,10 +42,14 @@ class PdfScrollable extends StatefulWidget { this.isBookmarkViewOpen, this.textDirection, this.child, + this.initiateTileRendering, {Key? key, this.onDoubleTap}) : super(key: key); + /// Transformation controller of PdfViewer. + final TransformationController transformationController; + /// Indicates whether page navigation dialog must be shown or not. final bool canShowPaginationDialog; @@ -115,6 +120,9 @@ class PdfScrollable extends StatefulWidget { ///A direction of text flow. final TextDirection textDirection; + /// Callback to initiate tile rendering. + final VoidCallback? initiateTileRendering; + @override PdfScrollableState createState() => PdfScrollableState(); } @@ -154,7 +162,7 @@ class PdfScrollableState extends State { @override void initState() { super.initState(); - _transformationController = TransformationController(); + _transformationController = widget.transformationController; currentOffset = Offset.zero; currentZoomLevel = 1; if (widget.pdfViewerController.zoomLevel > 1) { @@ -166,7 +174,6 @@ class PdfScrollableState extends State { @override void dispose() { - _transformationController.dispose(); _scrollTimer?.cancel(); _scrollTimer = null; super.dispose(); @@ -209,6 +216,7 @@ class PdfScrollableState extends State { onInteractionEnd: _handleInteractionEnd, transformationController: _transformationController, onPdfOffsetChanged: _handlePdfOffsetChanged, + initiateTileRendering: widget.initiateTileRendering, key: scrollHeadStateKey, ); } @@ -301,6 +309,8 @@ class PdfScrollableState extends State { isZoomChanged = true; }); } + + widget.initiateTileRendering?.call(); } ///Triggers when scrolling performed by touch pad. @@ -383,6 +393,9 @@ class PdfScrollableState extends State { /// Set the pixel in the matrix. void _setPixel(Offset offset) { + if (!mounted) { + return; + } final double widthFactor = widget.pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); offset = Offset( diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart index ca417cde4..cd56659d2 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart @@ -12,6 +12,7 @@ import '../../pdfviewer.dart'; import '../annotation/annotation.dart'; import '../annotation/text_markup.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; import 'pdf_page_view.dart'; import 'pdf_scrollable.dart'; import 'single_page_view.dart'; @@ -444,12 +445,17 @@ class CanvasRenderBox extends RenderBox { } SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; ThemeData? _themeData; SfLocalizations? _localizations; Future _showMobileHyperLinkDialog(Uri url) { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _themeData = Theme.of(context); + final bool isMaterial3 = _themeData!.useMaterial3; _localizations = SfLocalizations.of(context); return showDialog( context: context, @@ -468,6 +474,7 @@ class CanvasRenderBox extends RenderBox { : const EdgeInsets.all(6), backgroundColor: _pdfViewerThemeData! .hyperlinkDialogStyle?.backgroundColor ?? + _effectiveThemeData!.hyperlinkDialogStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF424242)), @@ -475,39 +482,61 @@ class CanvasRenderBox extends RenderBox { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(_localizations!.pdfHyperlinkLabel, - style: Theme.of(context) - .textTheme - .headlineMedium! - .copyWith( - fontSize: 20, - color: - Theme.of(context).brightness == Brightness.light + style: isMaterial3 + ? Theme.of(context) + .textTheme + .headlineMedium! + .copyWith( + fontSize: 24, + color: Theme.of(context).brightness == + Brightness.light ? Colors.black.withOpacity(0.87) : Colors.white.withOpacity(0.87), - ) - .merge(_pdfViewerThemeData! - .hyperlinkDialogStyle?.headerTextStyle)), + ) + .merge(_pdfViewerThemeData! + .hyperlinkDialogStyle?.headerTextStyle) + : Theme.of(context) + .textTheme + .headlineMedium! + .copyWith( + fontSize: 20, + color: Theme.of(context).brightness == + Brightness.light + ? Colors.black.withOpacity(0.87) + : Colors.white.withOpacity(0.87), + ) + .merge(_pdfViewerThemeData! + .hyperlinkDialogStyle?.headerTextStyle)), SizedBox( - height: 36, - width: 36, + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, child: RawMaterialButton( onPressed: () { Navigator.of(context).pop(); _isHyperLinkTapped = false; }, + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), child: Icon( Icons.clear, color: _pdfViewerThemeData! .hyperlinkDialogStyle?.closeIconColor ?? + _effectiveThemeData! + .hyperlinkDialogStyle?.closeIconColor ?? _themeData!.colorScheme.onSurface.withOpacity(0.6), - size: 24, + size: isMaterial3 ? 32 : 24, ), ), ) ], ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), + shape: isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), content: SingleChildScrollView( child: SizedBox( width: 296, @@ -570,12 +599,21 @@ class CanvasRenderBox extends RenderBox { Navigator.of(context).pop(); _isHyperLinkTapped = false; }, + style: isMaterial3 + ? ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ), + ) + : null, child: Text(_localizations!.pdfHyperlinkDialogCancelLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.6) @@ -594,12 +632,22 @@ class CanvasRenderBox extends RenderBox { mode: LaunchMode.externalApplication, ); }, + style: isMaterial3 + ? ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ), + ) + : null, child: Text(_localizations!.pdfHyperlinkDialogOpenLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: + isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary) .merge(_pdfViewerThemeData! .hyperlinkDialogStyle?.openTextStyle)), @@ -613,6 +661,10 @@ class CanvasRenderBox extends RenderBox { Future _showDesktopHyperLinkDialog(Uri url) { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + final bool isMaterial3 = Theme.of(context).useMaterial3; + _effectiveThemeData = isMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _themeData = Theme.of(context); _localizations = SfLocalizations.of(context); return showDialog( @@ -623,11 +675,14 @@ class CanvasRenderBox extends RenderBox { child: AlertDialog( scrollable: true, insetPadding: EdgeInsets.zero, - titlePadding: const EdgeInsets.all(16), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), + titlePadding: isMaterial3 ? null : const EdgeInsets.all(16), + contentPadding: isMaterial3 + ? null + : const EdgeInsets.symmetric(horizontal: 16), buttonPadding: const EdgeInsets.all(24), backgroundColor: _pdfViewerThemeData! .hyperlinkDialogStyle?.backgroundColor ?? + _effectiveThemeData!.hyperlinkDialogStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF424242)), @@ -639,35 +694,42 @@ class CanvasRenderBox extends RenderBox { .textTheme .headlineMedium! .copyWith( - fontSize: 20, - color: - Theme.of(context).brightness == Brightness.light + fontSize: isMaterial3 ? 24 : 20, + color: isMaterial3 + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).brightness == + Brightness.light ? Colors.black.withOpacity(0.87) : Colors.white.withOpacity(0.87), ) .merge(_pdfViewerThemeData! .hyperlinkDialogStyle?.headerTextStyle)), SizedBox( - height: 36, - width: 36, + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, child: RawMaterialButton( onPressed: () { Navigator.of(context).pop(); _isHyperLinkTapped = false; }, + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ) + : const RoundedRectangleBorder(), child: Icon( Icons.clear, color: _pdfViewerThemeData! .hyperlinkDialogStyle?.closeIconColor ?? + _effectiveThemeData! + .hyperlinkDialogStyle?.closeIconColor ?? _themeData!.colorScheme.onSurface.withOpacity(0.6), - size: 24, + size: isMaterial3 ? 30 : 24, ), ), ) ], ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), content: SingleChildScrollView( child: SizedBox( width: 361, @@ -678,7 +740,9 @@ class CanvasRenderBox extends RenderBox { ? Alignment.centerRight : Alignment.centerLeft, child: Padding( - padding: const EdgeInsets.fromLTRB(2, 0, 0, 8), + padding: isMaterial3 + ? const EdgeInsets.fromLTRB(2, 0, 0, 2) + : const EdgeInsets.fromLTRB(2, 0, 0, 8), child: Text(_localizations!.pdfHyperlinkContentLabel, style: Theme.of(context) .textTheme @@ -726,12 +790,20 @@ class CanvasRenderBox extends RenderBox { Navigator.of(context).pop(); _isHyperLinkTapped = false; }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text(_localizations!.pdfHyperlinkDialogCancelLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.6) @@ -748,12 +820,20 @@ class CanvasRenderBox extends RenderBox { mode: LaunchMode.externalApplication, ); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text(_localizations!.pdfHyperlinkDialogOpenLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary) .merge(_pdfViewerThemeData! .hyperlinkDialogStyle?.openTextStyle)), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart index eaeccbafb..d0242358e 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; /// Size of the ScrollHead. const double kPdfScrollHeadSize = 36.0; @@ -56,16 +57,23 @@ class ScrollHead extends StatefulWidget { /// State for [ScrollHead] class _ScrollHeadState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; + bool _isMaterial3 = false; @override void didChangeDependencies() { + _isMaterial3 = Theme.of(context).useMaterial3; _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = _isMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); super.didChangeDependencies(); } @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; super.dispose(); } @@ -164,6 +172,7 @@ class _ScrollHeadState extends State { child: Container( decoration: BoxDecoration( color: _pdfViewerThemeData!.scrollHeadStyle?.backgroundColor ?? + _effectiveThemeData!.scrollHeadStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? const Color(0xFFFAFAFA) @@ -180,7 +189,7 @@ class _ScrollHeadState extends State { .textTheme .bodySmall! .copyWith( - fontSize: 12, + fontSize: _isMaterial3 ? 14 : 12, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.87) : Colors.white.withOpacity(0.87), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart index 03e143244..b18a09d27 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head_overlay.dart @@ -4,8 +4,10 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/interactive_scroll_viewer_internal.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; + import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; import 'pdf_page_view.dart'; import 'pdf_scrollable.dart'; import 'scroll_head.dart'; @@ -52,6 +54,7 @@ class ScrollHeadOverlay extends StatefulWidget { this.onInteractionUpdate, this.onInteractionEnd, this.onPdfOffsetChanged, + this.initiateTileRendering, this.isPanEnabled = true}) : super(key: key); @@ -138,6 +141,9 @@ class ScrollHeadOverlay extends StatefulWidget { /// Direction of text flow. final TextDirection textDirection; + /// Callback to initiate tile rendering. + final VoidCallback? initiateTileRendering; + @override ScrollHeadOverlayState createState() => ScrollHeadOverlayState(); } @@ -147,10 +153,12 @@ class ScrollHeadOverlayState extends State { final TextEditingController _textFieldController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; ThemeData? _themeData; SfLocalizations? _localizations; final GlobalKey _childKey = GlobalKey(); Timer? _scrollTimer; + EdgeInsets _boundaryMargin = EdgeInsets.zero; /// Indicates whether the user interaction has ended. bool _isInteractionEnded = true; @@ -180,6 +188,9 @@ class ScrollHeadOverlayState extends State { @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _themeData = Theme.of(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); @@ -188,6 +199,7 @@ class ScrollHeadOverlayState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; _focusNode.dispose(); _scrollTimer?.cancel(); @@ -284,6 +296,7 @@ class ScrollHeadOverlayState extends State { widget.pdfViewerController.zoomLevel)) .abs())); } + widget.initiateTileRendering?.call(); return offset; } @@ -310,6 +323,7 @@ class ScrollHeadOverlayState extends State { onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, transformationController: widget.transformationController, key: _childKey, + boundaryMargin: _boundaryMargin, enableDoubleTapZooming: enableDoubleTapZoom, scaleEnabled: // ignore: avoid_bool_literals_in_conditional_expressions @@ -397,6 +411,7 @@ class ScrollHeadOverlayState extends State { /// Show the pagination dialog box Future _showPaginationDialog() async { + final bool isMaterial3 = Theme.of(context).useMaterial3; return showDialog( context: context, builder: (BuildContext context) { @@ -406,14 +421,17 @@ class ScrollHeadOverlayState extends State { child: AlertDialog( scrollable: true, insetPadding: EdgeInsets.zero, - contentPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(24) - : const EdgeInsets.only(right: 24, left: 24), + contentPadding: isMaterial3 + ? null + : orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(right: 24, left: 24), buttonPadding: orientation == Orientation.portrait ? const EdgeInsets.all(8) : const EdgeInsets.all(4), backgroundColor: _pdfViewerThemeData! .paginationDialogStyle?.backgroundColor ?? + _effectiveThemeData!.paginationDialogStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF424242)), @@ -422,23 +440,54 @@ class ScrollHeadOverlayState extends State { .textTheme .headlineMedium! .copyWith( - fontSize: 20, + fontSize: isMaterial3 ? 24 : 20, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.87) : Colors.white.withOpacity(0.87), ) .merge(_pdfViewerThemeData! .paginationDialogStyle?.headerTextStyle)), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: - SingleChildScrollView(child: _paginationTextField(context)), + shape: isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), + content: SingleChildScrollView( + child: Column( + children: [ + if (isMaterial3) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '1 - ${widget.pdfViewerController.pageCount}', + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith( + fontSize: 16, + color: Theme.of(context).brightness == + Brightness.light + ? Colors.black + : Colors.white), + )), + ), + _paginationTextField(context), + ], + )), actions: [ TextButton( onPressed: () { _textFieldController.clear(); Navigator.of(context).pop(); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text( _localizations!.pdfPaginationDialogCancelLabel, style: Theme.of(context) @@ -446,6 +495,7 @@ class ScrollHeadOverlayState extends State { .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -456,6 +506,13 @@ class ScrollHeadOverlayState extends State { onPressed: () { _handlePageNumberValidation(); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text( _localizations!.pdfPaginationDialogOkLabel, style: Theme.of(context) @@ -463,6 +520,7 @@ class ScrollHeadOverlayState extends State { .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -477,6 +535,7 @@ class ScrollHeadOverlayState extends State { /// A material design Text field for pagination dialog box. Widget _paginationTextField(BuildContext context) { + final bool isMaterial3 = Theme.of(context).useMaterial3; return Form( key: _formKey, child: SizedBox( @@ -496,10 +555,44 @@ class ScrollHeadOverlayState extends State { focusNode: _focusNode, decoration: InputDecoration( isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: _themeData!.colorScheme.primary), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 6), + border: isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, + )) + : null, + errorBorder: isMaterial3 + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(3.5), + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.errorBorderColor ?? + _themeData!.colorScheme.error, + )) + : null, + focusedBorder: isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _themeData!.colorScheme.primary, + width: 2), + ) + : UnderlineInputBorder( + borderSide: + BorderSide(color: _themeData!.colorScheme.primary), + ), + contentPadding: isMaterial3 + ? const EdgeInsets.all(16) + : const EdgeInsets.symmetric(vertical: 6), hintText: _localizations!.pdfEnterPageNumberLabel, hintStyle: Theme.of(context) .textTheme @@ -512,8 +605,9 @@ class ScrollHeadOverlayState extends State { ) .merge( _pdfViewerThemeData!.paginationDialogStyle?.hintTextStyle), - counterText: - '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterText: isMaterial3 + ? null + : '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', counterStyle: Theme.of(context) .textTheme .bodySmall! @@ -640,6 +734,7 @@ class ScrollHeadOverlayState extends State { void _handleScrollHeadDragEnd(DragEndDetails details) { _isInteractionEnded = true; isScrollHeadDragged = false; + widget.initiateTileRendering?.call(); } /// handles interaction start. @@ -653,6 +748,23 @@ class ScrollHeadOverlayState extends State { if (details.scale != 1) { _isInteractionEnded = false; } + if (details.scale < 1 && widget.pdfViewerController.zoomLevel > 1) { + final double verticalMargin = widget.totalImageSize.height < + widget.viewportDimension.height + ? (widget.viewportDimension.height - widget.totalImageSize.height) / 2 + : 0; + final double horizontalMargin = widget.totalImageSize.width < + widget.viewportDimension.width + ? (widget.viewportDimension.width - widget.totalImageSize.width) / 2 + : 0; + _boundaryMargin = EdgeInsets.only( + top: verticalMargin, + bottom: verticalMargin, + left: horizontalMargin, + right: horizontalMargin); + } else { + _boundaryMargin = EdgeInsets.zero; + } widget.onInteractionUpdate?.call(details); } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart index 23f465147..4ff80a913 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../../pdfviewer.dart'; +import '../theme/theme.dart'; /// Bottom position of the [ScrollStatus] widget. const double _kPdfScrollStatusBottomPosition = 25.0; @@ -25,11 +26,15 @@ class ScrollStatus extends StatefulWidget { /// State for [ScrollStatus] class _ScrollStatusState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -37,6 +42,7 @@ class _ScrollStatusState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; super.dispose(); } @@ -58,12 +64,13 @@ class _ScrollStatusState extends State { maxWidth: MediaQuery.of(context).size.width * 0.7, ), decoration: BoxDecoration( - color: - _pdfViewerThemeData!.scrollStatusStyle?.backgroundColor ?? - const Color(0xFF757575), - borderRadius: const BorderRadius.all( - Radius.circular(16.0), - ), + color: _pdfViewerThemeData! + .scrollStatusStyle?.backgroundColor ?? + _effectiveThemeData!.scrollStatusStyle?.backgroundColor ?? + const Color(0xFF757575), + borderRadius: Theme.of(context).useMaterial3 + ? const BorderRadius.all(Radius.circular(4.0)) + : const BorderRadius.all(Radius.circular(16.0)), ), child: Text( '${widget.pdfViewerController.pageNumber} ${_localizations!.pdfScrollStatusOfLabel} ${widget.pdfViewerController.pageCount}', @@ -73,11 +80,13 @@ class _ScrollStatusState extends State { .textTheme .titleMedium! .copyWith( - fontSize: 16, + fontSize: Theme.of(context).useMaterial3 ? 14 : 16, color: Colors.white, ) .merge(_pdfViewerThemeData! - .scrollStatusStyle?.pageInfoTextStyle)), + .scrollStatusStyle?.pageInfoTextStyle ?? + _effectiveThemeData! + .scrollStatusStyle?.pageInfoTextStyle)), ), ], ), diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart index bf227d390..54877da94 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart @@ -1,11 +1,14 @@ import 'dart:ui'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/interactive_scroll_viewer_internal.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; + import '../../pdfviewer.dart'; import '../common/pdfviewer_helper.dart'; +import '../theme/theme.dart'; import 'pdf_page_view.dart'; import 'pdf_scrollable.dart'; import 'scroll_head.dart'; @@ -30,6 +33,7 @@ class SinglePageView extends StatefulWidget { const SinglePageView( Key key, this.pdfViewerController, + this.transformationController, this.pageController, this.onPageChanged, this.interactionUpdate, @@ -50,12 +54,16 @@ class SinglePageView extends StatefulWidget { this.textDirection, this.isTablet, this.scrollDirection, + this.onInteractionEnd, this.children) : super(key: key); /// PdfViewer controller of PdfViewer. final PdfViewerController pdfViewerController; + /// Transformation controller of PdfViewer. + final TransformationController transformationController; + /// Page controller of the PdfViewer. final PageController pageController; @@ -120,6 +128,9 @@ class SinglePageView extends StatefulWidget { /// Represents the scroll direction final PdfScrollDirection scrollDirection; + /// Triggered when interaction end. + final VoidCallback? onInteractionEnd; + @override SinglePageViewState createState() => SinglePageViewState(); } @@ -127,6 +138,7 @@ class SinglePageView extends StatefulWidget { /// SinglePageView state class. class SinglePageViewState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; SfLocalizations? _localizations; double _scrollHeadPosition = 0; bool _canScroll = false; @@ -135,8 +147,7 @@ class SinglePageViewState extends State { double _paddingWidthScale = 0; double _paddingHeightScale = 0; Offset _currentOffsetOfInteractionUpdate = Offset.zero; - TransformationController _transformationController = - TransformationController(); + late TransformationController _transformationController; final TextEditingController _textFieldController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); final FocusNode _focusNode = FocusNode(); @@ -181,12 +192,16 @@ class SinglePageViewState extends State { @override void initState() { + _transformationController = widget.transformationController; super.initState(); } @override void didChangeDependencies() { _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _effectiveThemeData = Theme.of(context).useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); super.didChangeDependencies(); } @@ -194,6 +209,7 @@ class SinglePageViewState extends State { @override void dispose() { _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; _focusNode.dispose(); super.dispose(); @@ -384,6 +400,16 @@ class SinglePageViewState extends State { : (imageSize.round() <= widget.viewportDimension.height.round() ? (childSize.height - widget.viewportDimension.height) / 2 : _topMargin), + left: (widget.pdfViewerController.zoomLevel > 1 && + isHeightFitted && + _fingersInteracting > 1) + ? (widget.viewportDimension.width - childSize.width) / 2 + : 0, + right: (widget.pdfViewerController.zoomLevel > 1 && + isHeightFitted && + _fingersInteracting > 1) + ? (widget.viewportDimension.width - childSize.width) / 2 + : 0, ), constrained: false, onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, @@ -429,31 +455,6 @@ class SinglePageViewState extends State { widget.interactionUpdate( _transformationController.value.getMaxScaleOnAxis()); } - final double currentScale = - _transformationController.value.getMaxScaleOnAxis(); - if (details.scale <= 1) { - if (((kIsDesktop && !widget.isMobileWebView) || - (widget.viewportDimension.width > - widget.viewportDimension.height)) && - widget.viewportDimension.width.round() >= - (widget.pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize.width * - currentScale) - .round()) { - setState(() { - _paddingWidthScale = details.scale * currentScale; - }); - } - if (widget.viewportDimension.height.round() >= - (widget.pdfPages[widget.pdfViewerController.pageNumber]! - .pageSize.height * - _transformationController.value.getMaxScaleOnAxis()) - .round()) { - setState(() { - _paddingHeightScale = (details.scale) * currentScale; - }); - } - } if (details.scale == 1) { if (_transformationController .toScene( @@ -541,6 +542,7 @@ class SinglePageViewState extends State { _canJumpPrevious = false; _canJumpNext = false; _isZoomChanged = false; + widget.onInteractionEnd?.call(); setState(() {}); }, )); @@ -649,7 +651,7 @@ class SinglePageViewState extends State { // ignore: avoid_bool_literals_in_conditional_expressions widget.textDirection == TextDirection.ltr ? false : true, onPageChanged: (int value) { - _transformationController = TransformationController(); + _transformationController.value = Matrix4.identity(); widget.onPageChanged(value); }, physics: @@ -904,6 +906,7 @@ class SinglePageViewState extends State { } setState(() {}); + widget.onInteractionEnd?.call(); return offset; } @@ -1082,6 +1085,7 @@ class SinglePageViewState extends State { /// Show the pagination dialog box Future _showPaginationDialog() async { + final bool isMaterial3 = Theme.of(context).useMaterial3; return showDialog( context: context, builder: (BuildContext context) { @@ -1089,13 +1093,17 @@ class SinglePageViewState extends State { return AlertDialog( scrollable: true, insetPadding: EdgeInsets.zero, - contentPadding: orientation == Orientation.portrait - ? const EdgeInsets.all(24) - : const EdgeInsets.only(right: 24, left: 24), + contentPadding: isMaterial3 + ? null + : orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(right: 24, left: 24), buttonPadding: orientation == Orientation.portrait ? const EdgeInsets.all(8) : const EdgeInsets.all(4), - backgroundColor: _pdfViewerThemeData!.backgroundColor ?? + backgroundColor: _pdfViewerThemeData! + .paginationDialogStyle?.backgroundColor ?? + _effectiveThemeData!.paginationDialogStyle?.backgroundColor ?? (Theme.of(context).colorScheme.brightness == Brightness.light ? Colors.white : const Color(0xFF424242)), @@ -1104,43 +1112,86 @@ class SinglePageViewState extends State { .textTheme .headlineMedium! .copyWith( - fontSize: 20, + fontSize: isMaterial3 ? 24 : 20, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.87) : Colors.white.withOpacity(0.87), ) .merge(_pdfViewerThemeData! .paginationDialogStyle?.headerTextStyle)), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), - content: SingleChildScrollView(child: _paginationTextField()), + shape: isMaterial3 + ? null + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), + content: SingleChildScrollView( + child: Column( + children: [ + if (isMaterial3) + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Align( + alignment: Alignment.centerLeft, + child: Text( + '1 - ${widget.pdfViewerController.pageCount}', + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith( + fontSize: 16, + color: Theme.of(context).brightness == + Brightness.light + ? Colors.black + : Colors.white), + )), + ), + _paginationTextField(), + ], + )), actions: [ TextButton( onPressed: () { _textFieldController.clear(); Navigator.of(context).pop(); }, - child: Text(_localizations!.pdfPaginationDialogCancelLabel, - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - color: Theme.of(context).colorScheme.primary, - ) - .merge(_pdfViewerThemeData! - .paginationDialogStyle?.cancelTextStyle)), + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, + child: Text( + _localizations!.pdfPaginationDialogCancelLabel, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: Theme.of(context).colorScheme.primary, + ) + .merge(_pdfViewerThemeData! + .paginationDialogStyle?.cancelTextStyle), + ), ), TextButton( onPressed: () { _handlePageNumberValidation(); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text(_localizations!.pdfPaginationDialogOkLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: Theme.of(context).colorScheme.primary) .merge(_pdfViewerThemeData! .paginationDialogStyle?.okTextStyle)), @@ -1152,6 +1203,7 @@ class SinglePageViewState extends State { /// A material design Text field for pagination dialog box. Widget _paginationTextField() { + final bool isMaterial3 = Theme.of(context).useMaterial3; return Form( key: _formKey, child: SizedBox( @@ -1171,11 +1223,44 @@ class SinglePageViewState extends State { focusNode: _focusNode, decoration: InputDecoration( isDense: true, - focusedBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: Theme.of(context).colorScheme.primary), - ), - contentPadding: const EdgeInsets.symmetric(vertical: 6), + border: isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + Theme.of(context).colorScheme.primary, + )) + : null, + errorBorder: isMaterial3 + ? OutlineInputBorder( + borderRadius: BorderRadius.circular(3.5), + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.errorBorderColor ?? + Theme.of(context).colorScheme.error, + )) + : null, + focusedBorder: isMaterial3 + ? OutlineInputBorder( + borderSide: BorderSide( + color: _pdfViewerThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? + Theme.of(context).colorScheme.primary, + width: 2), + ) + : UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary), + ), + contentPadding: isMaterial3 + ? const EdgeInsets.all(16) + : const EdgeInsets.symmetric(vertical: 6), hintText: _localizations!.pdfEnterPageNumberLabel, hintStyle: Theme.of(context) .textTheme @@ -1188,8 +1273,9 @@ class SinglePageViewState extends State { ) .merge(_pdfViewerThemeData! .paginationDialogStyle?.hintTextStyle), - counterText: - '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterText: isMaterial3 + ? null + : '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', counterStyle: Theme.of(context) .textTheme .bodySmall! diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart index fc5add8f2..288fd1b39 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/text_selection_menu.dart @@ -47,9 +47,13 @@ class _TextSelectionMenuState extends State { return Container( height: kTextSelectionMenuHeight, decoration: ShapeDecoration( - color: (widget.themeData!.colorScheme.brightness == Brightness.light) - ? Colors.white - : const Color(0xFF303030), + color: widget.themeData!.useMaterial3 + ? (widget.themeData!.colorScheme.brightness == Brightness.light) + ? const Color.fromRGBO(238, 232, 244, 1) + : const Color.fromRGBO(48, 45, 56, 1) + : (widget.themeData!.colorScheme.brightness == Brightness.light) + ? Colors.white + : const Color(0xFF303030), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)), shadows: const [ BoxShadow( @@ -181,10 +185,13 @@ class _TextSelectionMenuItemState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), decoration: BoxDecoration( color: _isHovering - ? (widget.themeData!.colorScheme.brightness == - Brightness.light) - ? Colors.grey.withOpacity(0.2) - : Colors.grey.withOpacity(0.5) + ? widget.themeData!.useMaterial3 + ? widget.themeData!.colorScheme.onSurface + .withOpacity(0.08) + : (widget.themeData!.colorScheme.brightness == + Brightness.light) + ? Colors.grey.withOpacity(0.2) + : Colors.grey.withOpacity(0.5) : Colors.transparent, ), child: Row( @@ -198,9 +205,11 @@ class _TextSelectionMenuItemState extends State { overflow: TextOverflow.ellipsis, style: widget.themeData!.textTheme.bodyMedium!.copyWith( fontSize: 14, - color: widget.themeData!.brightness == Brightness.light - ? Colors.black.withOpacity(0.87) - : Colors.white.withOpacity(0.87), + color: widget.themeData!.useMaterial3 + ? widget.themeData!.colorScheme.onSurface + : widget.themeData!.brightness == Brightness.light + ? Colors.black.withOpacity(0.87) + : Colors.white.withOpacity(0.87), ), ), ], @@ -225,9 +234,11 @@ class _TextSelectionMenuItemState extends State { return ImageIcon( AssetImage('assets/$mode.png', package: 'syncfusion_flutter_pdfviewer'), size: 16, - color: (widget.themeData!.colorScheme.brightness == Brightness.light) - ? Colors.black.withOpacity(0.87) - : Colors.white.withOpacity(0.87), + color: widget.themeData!.useMaterial3 + ? widget.themeData!.colorScheme.onSurface + : (widget.themeData!.colorScheme.brightness == Brightness.light) + ? Colors.black.withOpacity(0.87) + : Colors.white.withOpacity(0.87), ); } } diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart index 860e2a39a..d14d16d68 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_checkbox.dart @@ -44,7 +44,7 @@ class PdfCheckboxFormField extends PdfFormField { class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { /// Initializes a new instance of the [PdfCheckboxFormFieldHelper] class. PdfCheckboxFormFieldHelper(this.pdfCheckboxField, int pageIndex, - {this.onValueChanged}) + {this.pdfCheckBoxItem, this.onValueChanged}) : super(pdfCheckboxField, pageIndex) { bounds = pdfCheckboxField.bounds; } @@ -52,6 +52,9 @@ class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { /// The checkbox field object from PDF library. final PdfCheckBoxField pdfCheckboxField; + /// Gets or sets the checked state of the [PdfCheckboxFormField]. + final PdfCheckBoxItem? pdfCheckBoxItem; + /// The callback which is called when the value of the form field is changed. final PdfFormFieldValueChangedCallback? onValueChanged; @@ -64,10 +67,19 @@ class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { checkboxFormField._children = groupedItems; } + /// Updates the child value. + void import() { + if (pdfCheckBoxItem != null) { + checkboxFormField._isChecked = pdfCheckBoxItem!.checked; + } + } + /// Creates the checkbox form field object. PdfCheckboxFormField getFormField() { checkboxFormField = PdfCheckboxFormField._() - .._isChecked = pdfCheckboxField.isChecked; + .._isChecked = pdfCheckBoxItem != null + ? pdfCheckBoxItem!.checked + : pdfCheckboxField.isChecked; super.load(checkboxFormField); return checkboxFormField; @@ -82,13 +94,6 @@ class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { onValueChanged!(PdfFormFieldValueChangedDetails( checkboxFormField, oldValue, newValue)); } - - if (checkboxFormField._children != null && - checkboxFormField._children!.isNotEmpty) { - for (final PdfCheckboxFormField item in checkboxFormField._children!) { - item.isChecked = newValue; - } - } rebuild(); } } @@ -96,7 +101,32 @@ class PdfCheckboxFormFieldHelper extends PdfFormFieldHelper { /// Sets the checkbox value. void setCheckboxValue(bool isChecked) { checkboxFormField._isChecked = isChecked; - pdfCheckboxField.isChecked = isChecked; + if (pdfCheckBoxItem != null) { + pdfCheckBoxItem!.checked = isChecked; + _updateChildItems(); + } else { + pdfCheckboxField.isChecked = isChecked; + } + } + + /// Updates the grouped field items. + void _updateChildItems() { + if (checkboxFormField._children != null && + checkboxFormField._children!.isNotEmpty) { + for (int index = 0; + index < checkboxFormField._children!.length; + index++) { + final PdfCheckboxFormFieldHelper helper = + PdfFormFieldHelper.getHelper(checkboxFormField._children![index]) + as PdfCheckboxFormFieldHelper; + + if (helper.pdfCheckBoxItem != null) { + checkboxFormField._children![index]._isChecked = + helper.pdfCheckBoxItem!.checked; + helper.rebuild(); + } + } + } } /// Builds the checkbox form field widget. diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart index 721dfc09a..edab74a2e 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_combo_box.dart @@ -195,6 +195,7 @@ class _PdfComboBoxState extends State { widget.onValueChanged!(newValue); } }, + style: Theme.of(context).textTheme.bodyMedium, selectedItemBuilder: (BuildContext context) { return widget.items.map((String value) { return Padding( diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart index c5f29848b..5d872308e 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_list_box.dart @@ -227,6 +227,7 @@ class _PdfListBoxState extends State { /// Shows the list box dialog when the list box form field is tapped. void _showListBoxDialog( BuildContext context, PdfListBoxFormFieldHelper listBoxHelper) { + final bool isMaterial3 = Theme.of(context).useMaterial3; List newItems = List.from(listBoxHelper.listBoxFormField._selectedItems!); showDialog( @@ -234,7 +235,12 @@ void _showListBoxDialog( context: context, builder: (BuildContext context) { return AlertDialog( - contentPadding: EdgeInsets.zero, + contentPadding: isMaterial3 + ? const EdgeInsets.only(left: 24.0, right: 16.0, top: 16.0) + : EdgeInsets.zero, + actionsPadding: isMaterial3 + ? const EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0) + : null, content: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SizedBox( @@ -250,6 +256,13 @@ void _showListBoxDialog( listBoxHelper.listBoxFormField._items[index]), value: newItems.contains( listBoxHelper.listBoxFormField._items[index]), + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4.0), + ) + : null, + contentPadding: + isMaterial3 ? EdgeInsets.zero : null, onChanged: (bool? value) { setState(() { if (value != null) { @@ -290,6 +303,16 @@ void _showListBoxDialog( onPressed: () { Navigator.pop(context); }, + style: isMaterial3 + ? TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 10), + fixedSize: const Size(double.infinity, 40), + ) + : null, child: const Text( 'CANCEL', style: TextStyle( @@ -301,6 +324,16 @@ void _showListBoxDialog( Navigator.pop(context); listBoxHelper.invokeValueChanged(newItems); }, + style: isMaterial3 + ? TextButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 10), + fixedSize: const Size(74, 40), + ) + : null, child: const Text( 'OK', style: TextStyle( diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart index a1e3a7c2d..a35097148 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_radio_button.dart @@ -44,6 +44,9 @@ class PdfRadioFormFieldHelper extends PdfFormFieldHelper { /// The radio button form field object. late PdfRadioFormField radioFormField; + /// Flag to determine whether the form field can be reset to null value. + late final bool canReset; + /// Creates the radio button form field object. PdfRadioFormField getFormField() { final List items = []; @@ -53,7 +56,7 @@ class PdfRadioFormFieldHelper extends PdfFormFieldHelper { final String selectedValue = pdfRadioField.selectedIndex != -1 ? items[pdfRadioField.selectedIndex] : ''; - + canReset = selectedValue == ''; radioFormField = PdfRadioFormField._() .._items = items .._selectedItem = selectedValue; diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart index 79c85fc19..999c46eaa 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_signature.dart @@ -67,6 +67,12 @@ class PdfSignatureFormFieldHelper extends PdfFormFieldHelper { return signatureFormField; } + /// Sets the signature form field value. + void setSignature(Uint8List? signature) { + signatureFormField._signature = signature; + rebuild(); + } + /// Invokes the value changed callback. Future invokeValueChanged(Uint8List? newValue) async { if (!listEquals(signatureFormField._signature, newValue)) { @@ -232,6 +238,7 @@ void _showSignatureContextMenu( PdfSignatureFormFieldHelper signatureFieldHelper, Offset position, double height) { + final bool isMaterial3 = Theme.of(context).useMaterial3; final RenderBox overlay = Overlay.of(context).context.findRenderObject()! as RenderBox; final RenderBox button = context.findRenderObject()! as RenderBox; @@ -259,7 +266,9 @@ void _showSignatureContextMenu( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( - icon: const Icon(Icons.draw_sharp), + icon: isMaterial3 + ? const Icon(Icons.draw_outlined) + : const Icon(Icons.draw_sharp), splashColor: Colors.transparent, hoverColor: Colors.transparent, focusColor: Colors.transparent, @@ -269,7 +278,9 @@ void _showSignatureContextMenu( }, ), IconButton( - icon: const Icon(Icons.delete), + icon: isMaterial3 + ? const Icon(Icons.delete_outline) + : const Icon(Icons.delete), splashColor: Colors.transparent, hoverColor: Colors.transparent, focusColor: Colors.transparent, @@ -295,6 +306,7 @@ bool _isSignatureDrawn = false; /// Dialog view for signature pad void _showSignaturePadDialog( BuildContext context, PdfSignatureFormFieldHelper signatureFieldHelper) { + final bool isMaterial3 = Theme.of(context).useMaterial3; _addColors(); _isSignatureDrawn = false; final SfLocalizations localizations = SfLocalizations.of(context); @@ -316,18 +328,29 @@ void _showSignaturePadDialog( builder: (BuildContext context, StateSetter setState) { return AlertDialog( insetPadding: const EdgeInsets.all(12.0), + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28.0)) + : null, title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(localizations.pdfSignaturePadDialogHeaderTextLabel, - style: Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 16, - fontFamily: 'Roboto-Medium', - color: - Theme.of(context).brightness == Brightness.light - ? Colors.black.withOpacity(0.87) - : Colors.white.withOpacity(0.87), - )), + Text( + localizations.pdfSignaturePadDialogHeaderTextLabel, + style: isMaterial3 + ? Theme.of(context).textTheme.bodyLarge!.copyWith( + fontSize: 24, + color: Theme.of(context).colorScheme.onSurface, + ) + : Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 16, + fontFamily: 'Roboto-Medium', + color: Theme.of(context).brightness == + Brightness.light + ? Colors.black.withOpacity(0.87) + : Colors.white.withOpacity(0.87), + ), + ), InkWell( //ignore: sdk_version_set_literal onTap: () { @@ -339,11 +362,19 @@ void _showSignaturePadDialog( false)); } }, - child: const Icon(Icons.clear, size: 24.0), + borderRadius: + isMaterial3 ? BorderRadius.circular(20.0) : null, + child: isMaterial3 + ? const SizedBox.square( + dimension: 40, child: Icon(Icons.clear, size: 24)) + : const Icon(Icons.clear, size: 24.0), ) ], ), - titlePadding: const EdgeInsets.all(16.0), + titlePadding: isMaterial3 + ? const EdgeInsets.only( + left: 24.0, top: 16.0, right: 16.0, bottom: 16) + : const EdgeInsets.all(16.0), content: SingleChildScrollView( child: SizedBox( width: signaturePadWidth, @@ -353,7 +384,15 @@ void _showSignaturePadDialog( Container( height: signaturePadHeight, decoration: BoxDecoration( - border: Border.all(color: Colors.grey[350]!), + border: isMaterial3 + ? Border.all( + color: Theme.of(context) + .colorScheme + .outlineVariant) + : Border.all(color: Colors.grey[350]!), + color: Colors.white, + borderRadius: + isMaterial3 ? BorderRadius.circular(4.0) : null, ), child: SfSignaturePad( strokeColor: _strokeColor, @@ -378,6 +417,8 @@ void _showSignaturePadDialog( .bodyMedium! .copyWith( fontSize: 14, + fontWeight: + isMaterial3 ? FontWeight.bold : null, fontFamily: 'Roboto-Regular', color: Theme.of(context).brightness == Brightness.light @@ -386,10 +427,11 @@ void _showSignaturePadDialog( ), ), SizedBox( - width: 124, + width: 128, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _addStrokeColorPalettes(setState), + children: + _addStrokeColorPalettes(setState, context), ), ) ], @@ -398,8 +440,12 @@ void _showSignaturePadDialog( ), ), ), - contentPadding: const EdgeInsets.symmetric(horizontal: 12.0), - actionsPadding: const EdgeInsets.all(8.0), + contentPadding: isMaterial3 + ? const EdgeInsets.symmetric(horizontal: 24.0) + : const EdgeInsets.symmetric(horizontal: 12.0), + actionsPadding: isMaterial3 + ? const EdgeInsets.all(24) + : const EdgeInsets.all(8.0), buttonPadding: EdgeInsets.zero, actions: [ TextButton( @@ -411,10 +457,21 @@ void _showSignaturePadDialog( _isSignatureDrawn = false; }); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 10), + ) + : null, child: Text( localizations.pdfSignaturePadDialogClearLabel, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, fontFamily: 'Roboto-Medium', color: themeData.colorScheme.primary, ), @@ -435,11 +492,28 @@ void _showSignaturePadDialog( false)); } }, + style: isMaterial3 + ? ElevatedButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.primary, + disabledBackgroundColor: + Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + ), + padding: const EdgeInsets.symmetric( + horizontal: 20.0, vertical: 10), + fixedSize: const Size(double.infinity, 40), + ) + : null, child: Text( localizations.pdfSignaturePadDialogSaveLabel, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 14, - color: themeData.colorScheme.primary, + fontWeight: isMaterial3 ? FontWeight.w500 : null, + color: isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.primary, fontFamily: 'Roboto-Medium', ), ), @@ -481,7 +555,9 @@ void _addColors() { } /// Add stroke color palettes -List _addStrokeColorPalettes(StateSetter stateChanged) { +List _addStrokeColorPalettes( + StateSetter stateChanged, BuildContext context) { + final bool isMaterial3 = Theme.of(context).useMaterial3; _strokeColorWidgets = []; for (int i = 0; i < _strokeColors.length; i++) { _strokeColorWidgets.add( @@ -499,15 +575,29 @@ List _addStrokeColorPalettes(StateSetter stateChanged) { _selectedPenIndex = i; }, ), + overlayColor: isMaterial3 + ? const MaterialStatePropertyAll(Colors.transparent) + : null, child: Center( child: Stack( children: [ - Icon(Icons.brightness_1, size: 25.0, color: _strokeColors[i]), + Padding( + padding: + isMaterial3 ? const EdgeInsets.all(4) : EdgeInsets.zero, + child: Icon(Icons.brightness_1, + size: isMaterial3 ? 24.0 : 25.0, + color: _strokeColors[i]), + ), if (_selectedPenIndex == i) - const Padding( - padding: EdgeInsets.all(5), - child: Icon(Icons.check, size: 15.0, color: Colors.white), - ) + isMaterial3 + ? Icon(Icons.circle_outlined, + size: 32.0, + color: Theme.of(context).colorScheme.primary) + : const Padding( + padding: EdgeInsets.all(5), + child: Icon(Icons.check, + size: 15.0, color: Colors.white), + ) else const SizedBox(width: 8), ], diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart index d695faecf..5ce345236 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/form_fields/pdf_text_box.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; import '../../pdfviewer.dart'; +import '../change_tracker/change_tracker.dart'; import 'pdf_form_field.dart'; /// Represents the text form field. @@ -71,12 +72,12 @@ class PdfTextFormFieldHelper extends PdfFormFieldHelper { late FocusNode focusNode; /// Creates the text form field object. - PdfTextFormField getFormField() { + PdfTextFormField getFormField(ChangeTracker changeTracker) { textFormField = PdfTextFormField._().._text = pdfTextField.text; super.load(textFormField); textEditingController = TextEditingController(text: textFormField._text); - focusNode = createFocusNode(); + focusNode = createFocusNode(changeTracker); return textFormField; } @@ -88,9 +89,34 @@ class PdfTextFormFieldHelper extends PdfFormFieldHelper { } /// Gets the focus node of the [PdfTextFormField]. - FocusNode createFocusNode() { - return FocusNode() - ..addListener(() { + FocusNode createFocusNode(ChangeTracker changeTracker) { + return FocusNode( + onKeyEvent: (FocusNode focusNode, KeyEvent event) { + final bool isControlOrMeta = + HardwareKeyboard.instance.isControlPressed || + HardwareKeyboard.instance.isMetaPressed; + final bool isLogicalOrPhysicalZ = + event.logicalKey == LogicalKeyboardKey.keyZ || + event.physicalKey == PhysicalKeyboardKey.keyZ; + final bool isLogicalOrPhysicalY = + event.logicalKey == LogicalKeyboardKey.keyY || + event.physicalKey == PhysicalKeyboardKey.keyY; + + if (isControlOrMeta && isLogicalOrPhysicalZ) { + if (event is KeyDownEvent) { + changeTracker.undoController.undo(); + } + return KeyEventResult.handled; + } else if (isControlOrMeta && isLogicalOrPhysicalY) { + if (event is KeyDownEvent) { + changeTracker.undoController.redo(); + } + return KeyEventResult.handled; + } else { + return KeyEventResult.ignored; + } + }, + )..addListener(() { if (!focusNode.hasFocus) { invokeFocusChange(focusNode.hasFocus); } @@ -110,13 +136,6 @@ class PdfTextFormFieldHelper extends PdfFormFieldHelper { onValueChanged!( PdfFormFieldValueChangedDetails(textFormField, oldValue, newValue)); } - - if (textFormField._children != null && - textFormField._children!.isNotEmpty) { - for (final PdfTextFormField item in textFormField._children!) { - item.text = newValue; - } - } rebuild(); } } @@ -137,9 +156,25 @@ class PdfTextFormFieldHelper extends PdfFormFieldHelper { if (textEditingController.text != text) { textEditingController.text = text; } + _updateChildItems(text); pdfTextField.text = text; } + /// Updates the grouped field items. + void _updateChildItems(String text) { + if (textFormField._children != null && + textFormField._children!.isNotEmpty) { + for (final PdfTextFormField item in textFormField._children!) { + final PdfFormFieldHelper childHelper = + PdfFormFieldHelper.getHelper(item); + if (childHelper is PdfTextFormFieldHelper && + childHelper.textEditingController.text != text) { + childHelper.textEditingController.text = text; + } + } + } + } + /// Builds the text form field widget. Widget build(BuildContext context, double heightPercentage, {required Function(Offset) onTap}) { diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart index ce5cde74a..5855f1fea 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart @@ -52,6 +52,7 @@ import 'form_fields/pdf_list_box.dart'; import 'form_fields/pdf_radio_button.dart'; import 'form_fields/pdf_signature.dart'; import 'form_fields/pdf_text_box.dart'; +import 'theme/theme.dart'; /// Signature for [SfPdfViewer.onTextSelectionChanged] callback. typedef PdfTextSelectionChangedCallback = void Function( @@ -180,6 +181,7 @@ class SfPdfViewer extends StatefulWidget { this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, this.scrollDirection, @@ -248,6 +250,7 @@ class SfPdfViewer extends StatefulWidget { this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, this.scrollDirection, @@ -314,6 +317,7 @@ class SfPdfViewer extends StatefulWidget { this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, this.scrollDirection, @@ -384,6 +388,7 @@ class SfPdfViewer extends StatefulWidget { this.canShowSignaturePadDialog = true, this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, + this.initialPageNumber = 1, this.maxZoomLevel = 3, this.interactionMode = PdfInteractionMode.selection, this.scrollDirection, @@ -446,6 +451,42 @@ class SfPdfViewer extends StatefulWidget { /// ``` final double initialZoomLevel; + /// Represents the initial page to be displayed when the [SfPdfViewer] widget is loaded. + /// + /// Defaults to 1.0 + /// + /// It is recommended not to use both the [initialScrollOffset] and [initialPageNumber] properties at the same time. If both properties are defined, then the [initialPageNumber] will be prioritized over the [initialScrollOffset]. + /// + /// ```dart + /// class MyAppState extends State{ + /// + /// late PdfViewerController _pdfViewerController; + /// + /// @override + /// void initState(){ + /// _pdfViewerController = PdfViewerController(); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: Text('Syncfusion Flutter PdfViewer'), + /// ), + /// body: SfPdfViewer.asset( + /// 'assets/flutter-succinctly.pdf', + /// controller: _pdfViewerController, + /// initialPageNumber: 2, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final int initialPageNumber; + /// Represents the maximum allowed zoom level. /// /// Defaults to 3.0. @@ -1120,9 +1161,15 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Offset _pagePointerDownPosition = Offset.zero; Duration _pagePointerDownTimeStamp = Duration.zero; bool _isDoubleTapped = false; + Size _viewportSize = Size.zero; + Timer? _tileTimer; + final Map _viewportPages = {}; + final Map _tileImages = {}; + late TransformationController _transformationController; /// PdfViewer theme data. SfPdfViewerThemeData? _pdfViewerThemeData; + SfPdfViewerThemeData? _effectiveThemeData; ///Color scheme data ThemeData? _themeData; @@ -1149,6 +1196,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { UndoHistoryController get _effectiveUndoController => widget.undoController ?? (_undoController ??= UndoHistoryController()); + /// Flag to indicate whether to skip adding the form field value change record in the undo stack. + bool _skipAddingFormFieldChange = false; + /// Text selection menu bool _isTextSelectionVisibleInViewport = false; int _selectedTextPageNumber = -1; @@ -1160,6 +1210,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); + _transformationController = TransformationController(); _plugin = PdfViewerPlugin(); _scrollDirection = widget.scrollDirection != null ? widget.scrollDirection! @@ -1189,6 +1240,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { super.didChangeDependencies(); _pdfViewerThemeData = SfPdfViewerTheme.of(context); _themeData = Theme.of(context); + _effectiveThemeData = _themeData!.useMaterial3 + ? SfPdfViewerThemeDataM3(context) + : SfPdfViewerThemeDataM2(context); _localizations = SfLocalizations.of(context); _isOrientationChanged = _deviceOrientation != null && _deviceOrientation != MediaQuery.of(context).orientation; @@ -1230,8 +1284,11 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { widget._provider.getPdfBytes(context), oldWidget.password); if (oldWidget.pageLayoutMode != widget.pageLayoutMode && oldWidget.controller != null) { + _tileImages.clear(); + _transformationController = TransformationController(); _updateOffsetOnLayoutChange(oldWidget.controller!.zoomLevel, oldWidget.controller!.scrollOffset, oldWidget.pageLayoutMode); + _getTileImage(); } } @@ -1275,6 +1332,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _matchedTextPageIndices.clear(); _extractedTextCollection.clear(); _pdfViewerThemeData = null; + _effectiveThemeData = null; _localizations = null; imageCache.clear(); _killTextSearchIsolate(); @@ -1303,6 +1361,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _changeTracker.dispose(); _pdfViewerController._removeListener(_handleControllerValueChange); _hideTextSelectionMenu(); + _transformationController.dispose(); WidgetsBinding.instance.removeObserver(this); super.dispose(); } @@ -1369,6 +1428,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _removedAnnotations.clear(); _annotationMap.clear(); _hideTextSelectionMenu(); + _tileImages.clear(); + _viewportPages.clear(); + _tileTimer?.cancel(); } /// Retrieves the form field details in the document @@ -1394,7 +1456,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { onValueChanged: _formFieldValueChanged); helper.bounds = item.bounds; - final PdfTextFormField textFormField = helper.getFormField(); + final PdfTextFormField textFormField = + helper.getFormField(_changeTracker); groupedTextFormFields.add(textFormField); _pdfViewerController._formFields.add(textFormField); _textBoxFocusNodes.add(helper.focusNode); @@ -1418,7 +1481,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { onFocusChanged: widget.onFormFieldFocusChange, onValueChanged: _formFieldValueChanged); - _pdfViewerController._formFields.add(helper.getFormField()); + _pdfViewerController._formFields + .add(helper.getFormField(_changeTracker)); _textBoxFocusNodes.add(helper.focusNode); } } @@ -1431,16 +1495,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { for (int j = 0; j < field.items!.count; j++) { final PdfFieldItem item = field.items![j]; final int itemPageIndex = _document!.pages.indexOf(item.page!); - - final PdfCheckboxFormFieldHelper groupedItemHelper = - PdfCheckboxFormFieldHelper(field, itemPageIndex, - onValueChanged: _formFieldValueChanged); - groupedItemHelper.bounds = item.bounds; - - final PdfCheckboxFormField groupedFormField = - groupedItemHelper.getFormField(); - groupedCheckBoxFormFields.add(groupedFormField); - _pdfViewerController._formFields.add(groupedFormField); + if (item is PdfCheckBoxItem) { + final PdfCheckboxFormFieldHelper groupedItemHelper = + PdfCheckboxFormFieldHelper(field, itemPageIndex, + pdfCheckBoxItem: item, + onValueChanged: _formFieldValueChanged); + groupedItemHelper.bounds = item.bounds; + final PdfCheckboxFormField groupedFormField = + groupedItemHelper.getFormField(); + groupedCheckBoxFormFields.add(groupedFormField); + _pdfViewerController._formFields.add(groupedFormField); + } } for (final PdfCheckboxFormField checkboxFormField @@ -1500,7 +1565,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (field is PdfListBoxField) { final PdfListBoxFormFieldHelper helper = PdfListBoxFormFieldHelper( field, pageIndex, - onValueChanged: widget.onFormFieldValueChanged); + onValueChanged: _formFieldValueChanged); _pdfViewerController._formFields.add(helper.getFormField()); } @@ -1509,21 +1574,36 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Called when the value of a form field is changed. void _formFieldValueChanged(PdfFormFieldValueChangedDetails details) { - if (widget.onFormFieldValueChanged != null) { + if (widget.onFormFieldValueChanged != null && + !_changeTracker.changeInProgress) { widget.onFormFieldValueChanged!(details); } - _changeGroupedFieldValue(details.formField); + if (!_skipAddingFormFieldChange) { + _changeTracker.addChange(FormFieldValueChangeTracker( + records: [ + FormFieldValueChangeRecord( + formField: details.formField, + oldValue: details.oldValue, + newValue: details.newValue) + ], + onUndoOrRedo: _updateFormField, + )); + } + _changeLinkedFieldValue(details.formField); } /// Change the value of grouped form fields. - void _changeGroupedFieldValue(PdfFormField editedField) { + void _changeLinkedFieldValue(PdfFormField editedField) { + _skipAddingFormFieldChange = true; for (final PdfFormField field in _pdfViewerController._formFields) { if (editedField != field && editedField.name == field.name) { if (editedField is PdfTextFormField && field is PdfTextFormField) { field.text = editedField.text; } else if (editedField is PdfCheckboxFormField && field is PdfCheckboxFormField) { - field.isChecked = editedField.isChecked; + if (editedField.children == null) { + field.isChecked = editedField.isChecked; + } } else if (editedField is PdfRadioFormField && field is PdfRadioFormField) { field.selectedItem = editedField.selectedItem; @@ -1539,10 +1619,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } } + _skipAddingFormFieldChange = false; } /// Imports the form fields details from the document. void _importFormFieldData() { + final List formFieldValueChangeRecords = + []; for (int fieldIndex = 0; fieldIndex < _document!.form.fields.count; fieldIndex++) { @@ -1552,22 +1635,32 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .where((PdfFormField formField) => formField.name == field.name) .toList(); + FormFieldValueChangeRecord? record; for (final PdfFormField formField in filteredFormFields) { if (field is PdfTextBoxField && formField is PdfTextFormField) { if (!formField.readOnly) { - (PdfFormFieldHelper.getHelper(formField) as PdfTextFormFieldHelper) - .setTextBoxValue(field.text); + record = _updateFormField(formField, field.text); } else { field.text = formField.text; } } + if (field is PdfCheckBoxField && formField is PdfCheckboxFormField) { - if (!formField.readOnly) { + if (formField.children == null) { + if (!formField.readOnly) { + record = _updateFormField(formField, field.isChecked); + } else { + field.isChecked = formField.isChecked; + } + } else { + final bool oldValue = formField.isChecked; (PdfFormFieldHelper.getHelper(formField) as PdfCheckboxFormFieldHelper) - .setCheckboxValue(field.isChecked); - } else { - field.isChecked = formField.isChecked; + .import(); + record = FormFieldValueChangeRecord( + formField: formField, + oldValue: oldValue, + newValue: formField.isChecked); } } if (field is PdfComboBoxField && formField is PdfComboBoxFormField) { @@ -1575,10 +1668,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { final String selectedItem = field.selectedIndex != -1 ? field.items[field.selectedIndex].text : ''; - - (PdfFormFieldHelper.getHelper(formField) - as PdfComboBoxFormFieldHelper) - .setComboBoxValue(selectedItem); + record = _updateFormField(formField, selectedItem); } else { field.selectedValue = formField.selectedItem; } @@ -1589,9 +1679,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { final String selectedItem = field.selectedIndex != -1 ? field.items[field.selectedIndex].value : ''; - - (PdfFormFieldHelper.getHelper(formField) as PdfRadioFormFieldHelper) - .setRadioButtonValue(selectedItem); + record = _updateFormField(formField, selectedItem); } else { field.selectedValue = formField.selectedItem; } @@ -1600,16 +1688,81 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (!formField.readOnly) { final List selectedItems = List.from(field.selectedValues, growable: false); - - (PdfFormFieldHelper.getHelper(formField) - as PdfListBoxFormFieldHelper) - .setListBoxValue(selectedItems); + record = _updateFormField(formField, selectedItems); } else { field.selectedValues = formField.selectedItems!; } } + if (record != null) { + formFieldValueChangeRecords.add(record); + } } } + if (formFieldValueChangeRecords.isNotEmpty) { + _changeTracker.addChange(FormFieldValueChangeTracker( + records: formFieldValueChangeRecords, + onUndoOrRedo: _updateFormField)); + } + } + + /// Update the form field values. + FormFieldValueChangeRecord? _updateFormField( + PdfFormField field, Object? value, + [bool isUndoOrRedo = false]) { + if (field.readOnly) { + return null; + } + Object? oldValue; + Object? newValue; + final PdfFormFieldHelper formFieldHelper = + PdfFormFieldHelper.getHelper(field); + if (formFieldHelper is PdfTextFormFieldHelper && + field is PdfTextFormField && + value is String) { + oldValue = field.text; + formFieldHelper.setTextBoxValue(value); + newValue = field.text; + } else if (formFieldHelper is PdfCheckboxFormFieldHelper && + field is PdfCheckboxFormField && + value is bool) { + oldValue = field.isChecked; + formFieldHelper.setCheckboxValue(value); + newValue = field.isChecked; + } else if (formFieldHelper is PdfRadioFormFieldHelper && + field is PdfRadioFormField && + value is String && + (value == '' || field.items.contains(value))) { + oldValue = field.selectedItem; + formFieldHelper.setRadioButtonValue(value); + newValue = field.selectedItem; + } else if (formFieldHelper is PdfComboBoxFormFieldHelper && + field is PdfComboBoxFormField && + value is String && + field.items.contains(value)) { + oldValue = field.selectedItem; + formFieldHelper.setComboBoxValue(value); + newValue = field.selectedItem; + } else if (formFieldHelper is PdfListBoxFormFieldHelper && + field is PdfListBoxFormField && + value is List) { + oldValue = field.selectedItems; + formFieldHelper.setListBoxValue(value); + newValue = field.selectedItems; + } else if (formFieldHelper is PdfSignatureFormFieldHelper && + field is PdfSignatureFormField && + value is Uint8List?) { + oldValue = field.signature; + formFieldHelper.setSignature(value); + newValue = field.signature; + } + _changeLinkedFieldValue(field); + formFieldHelper.rebuild(); + if (oldValue != newValue && !isUndoOrRedo) { + return FormFieldValueChangeRecord( + formField: field, oldValue: oldValue, newValue: newValue); + } else { + return null; + } } /// Retrieves the annotation details from the document. @@ -1710,7 +1863,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfBytes = Uint8List(0); _pdfBytes = Uint8List.fromList(await _document!.save()); - if (_isSignatureSaved) { + if (_isSignatureSaved || + _pdfViewerController._flattenOption == PdfFlattenOption.formFields) { _loadPdfDocument(true, true); _isSignatureSaved = false; } @@ -1955,37 +2109,53 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Show the password dialog box for web. Widget _showWebPasswordDialogue() { + final bool isMaterial3 = _themeData!.useMaterial3; return Container( - color: (_themeData!.colorScheme.brightness == Brightness.light) - ? const Color(0xFFD6D6D6) - : const Color(0xFF303030), + color: isMaterial3 + ? _themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFA19CA5) + : const Color(0xFF221F27) + : (_themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFD6D6D6) + : const Color(0xFF303030)), child: Visibility( visible: _visibility, child: Center( child: Container( - height: 230, - width: 345, + height: isMaterial3 ? 264 : 230, + width: isMaterial3 ? 360 : 345, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: (_themeData!.colorScheme.brightness == Brightness.light) - ? Colors.white - : const Color(0xFF424242)), + borderRadius: isMaterial3 + ? BorderRadius.circular(28) + : BorderRadius.circular(4), + color: isMaterial3 + ? _pdfViewerThemeData + ?.passwordDialogStyle?.backgroundColor ?? + _effectiveThemeData! + .passwordDialogStyle?.backgroundColor + : (_themeData!.colorScheme.brightness == Brightness.light) + ? Colors.white + : const Color(0xFF424242)), child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: isMaterial3 + ? MainAxisAlignment.start + : MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Padding( - padding: const EdgeInsets.only(left: 16, top: 10), + padding: isMaterial3 + ? const EdgeInsets.only(left: 24, top: 24) + : const EdgeInsets.only(left: 16, top: 10), child: Text( _localizations!.passwordDialogHeaderTextLabel, style: Theme.of(context) .textTheme .headlineMedium! .copyWith( - fontSize: 20, + fontSize: isMaterial3 ? 24 : 20, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.87) @@ -1996,10 +2166,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ), ), Padding( - padding: const EdgeInsets.fromLTRB(0, 10, 16, 0), + padding: isMaterial3 + ? const EdgeInsets.fromLTRB(0, 24, 16, 0) + : const EdgeInsets.fromLTRB(0, 10, 16, 0), child: SizedBox( - height: 36, - width: 36, + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, child: RawMaterialButton( onPressed: () { setState(() { @@ -2010,13 +2182,19 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _passwordVisible = true; }); }, + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40)) + : const RoundedRectangleBorder(), child: Icon( Icons.clear, color: _pdfViewerThemeData! .passwordDialogStyle?.closeIconColor ?? + _effectiveThemeData! + .passwordDialogStyle?.closeIconColor ?? _themeData!.colorScheme.onSurface .withOpacity(0.6), - size: 24, + size: isMaterial3 ? 28 : 24, ), ), ), @@ -2024,7 +2202,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ], ), Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + padding: isMaterial3 + ? const EdgeInsets.fromLTRB(24, 8, 24, 4) + : const EdgeInsets.fromLTRB(16, 0, 16, 8), child: Text(_localizations!.passwordDialogContentLabel, style: Theme.of(context) .textTheme @@ -2039,10 +2219,16 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .passwordDialogStyle?.contentTextStyle)), ), Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + padding: isMaterial3 + ? const EdgeInsets.fromLTRB(24, 16, 24, 8) + : const EdgeInsets.fromLTRB(16, 0, 16, 0), child: _textField()), Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 16, 10), + padding: isMaterial3 + ? _errorTextPresent + ? const EdgeInsets.fromLTRB(24, 0, 32, 8) + : const EdgeInsets.fromLTRB(24, 19, 32, 8) + : const EdgeInsets.fromLTRB(0, 0, 16, 10), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -2055,6 +2241,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _errorTextPresent = false; }); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text( _localizations!.pdfPasswordDialogCancelLabel, style: Theme.of(context) @@ -2062,6 +2255,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .bodyMedium! .copyWith( fontSize: 14, + fontWeight: + isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -2071,12 +2266,21 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { onPressed: () { _passwordValidation(_textFieldController.text); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text(_localizations!.pdfPasswordDialogOpenLabel, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( fontSize: 14, + fontWeight: + isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -2095,8 +2299,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { // TextFormField of password dialogue Widget _textField() { + final bool isMaterial3 = _themeData!.useMaterial3; return SizedBox( - width: 296, + width: isMaterial3 ? 312 : 296, child: TextFormField( style: Theme.of(context) .textTheme @@ -2112,26 +2317,37 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { obscureText: _passwordVisible, obscuringCharacter: '*', decoration: InputDecoration( + isDense: true, border: OutlineInputBorder( borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, color: _pdfViewerThemeData! .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? _themeData!.colorScheme.primary, )), errorBorder: OutlineInputBorder( borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, color: _pdfViewerThemeData!.passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData!.passwordDialogStyle?.errorBorderColor ?? _themeData!.colorScheme.error, )), focusedBorder: OutlineInputBorder( borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, color: _pdfViewerThemeData! .passwordDialogStyle?.inputFieldBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.inputFieldBorderColor ?? _themeData!.colorScheme.primary, )), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( + width: isMaterial3 ? 2 : 1, color: _pdfViewerThemeData!.passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData!.passwordDialogStyle?.errorBorderColor ?? _themeData!.colorScheme.error, )), hintText: _localizations!.passwordDialogHintTextLabel, @@ -2147,7 +2363,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ) .merge(_pdfViewerThemeData! .passwordDialogStyle?.inputFieldHintTextStyle), - labelText: _localizations!.passwordDialogHintTextLabel, + labelText: + isMaterial3 ? null : _localizations!.passwordDialogHintTextLabel, labelStyle: Theme.of(context) .textTheme .headlineMedium! @@ -2163,15 +2380,24 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .textTheme .bodyMedium! .copyWith( - fontSize: 14, + fontSize: isMaterial3 ? 12 : 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.error, ) .merge(_pdfViewerThemeData!.passwordDialogStyle?.errorTextStyle), suffixIcon: IconButton( icon: Icon( - _passwordVisible ? Icons.visibility : Icons.visibility_off, + _passwordVisible + ? (isMaterial3 + ? Icons.visibility_outlined + : Icons.visibility) + : (isMaterial3 + ? Icons.visibility_off_outlined + : Icons.visibility_off), color: _pdfViewerThemeData! .passwordDialogStyle?.visibleIconColor ?? + _effectiveThemeData! + .passwordDialogStyle?.visibleIconColor ?? Theme.of(context).colorScheme.onSurface.withOpacity(0.6)), onPressed: () { setState(() { @@ -2222,6 +2448,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Show the password dialog box for mobile Future _showPasswordDialog() async { final TextDirection textDirection = Directionality.of(context); + final bool isMaterial3 = _themeData!.useMaterial3; return showDialog( context: context, builder: (BuildContext context) { @@ -2237,11 +2464,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { buttonPadding: orientation == Orientation.portrait ? const EdgeInsets.all(8) : const EdgeInsets.all(4), - backgroundColor: _pdfViewerThemeData! - .passwordDialogStyle!.backgroundColor ?? - (Theme.of(context).colorScheme.brightness == Brightness.light - ? Colors.white - : const Color(0xFF424242)), + backgroundColor: + _pdfViewerThemeData!.passwordDialogStyle?.backgroundColor ?? + _effectiveThemeData!.passwordDialogStyle?.backgroundColor ?? + (isMaterial3 + ? _themeData!.colorScheme.brightness == Brightness.light + ? const Color(0xFFA19CA5) + : const Color(0xFF221F27) + : (Theme.of(context).colorScheme.brightness == + Brightness.light + ? Colors.white + : const Color(0xFF424242))), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -2252,7 +2485,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .textTheme .headlineMedium! .copyWith( - fontSize: 20, + fontSize: isMaterial3 ? 24 : 20, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: Theme.of(context).brightness == Brightness.light ? Colors.black.withOpacity(0.87) @@ -2263,27 +2497,36 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ), ), SizedBox( - height: 36, - width: 36, + height: isMaterial3 ? 40 : 36, + width: isMaterial3 ? 40 : 36, child: RawMaterialButton( onPressed: () { _focusNode.unfocus(); _textFieldController.clear(); Navigator.of(context).pop(); }, + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40)) + : const RoundedRectangleBorder(), child: Icon( Icons.clear, color: _pdfViewerThemeData! .passwordDialogStyle?.closeIconColor ?? + _effectiveThemeData! + .passwordDialogStyle?.closeIconColor ?? _themeData!.colorScheme.onSurface.withOpacity(0.6), - size: 24, + size: isMaterial3 ? 30 : 24, ), ), ), ], ), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(4.0))), + shape: isMaterial3 + ? RoundedRectangleBorder( + borderRadius: BorderRadius.circular(28)) + : const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), content: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SingleChildScrollView( @@ -2296,7 +2539,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ? Alignment.centerLeft : Alignment.centerRight, child: Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 24), + padding: isMaterial3 + ? const EdgeInsets.fromLTRB(0, 0, 0, 8) + : const EdgeInsets.fromLTRB(0, 0, 0, 24), child: Text( _localizations!.passwordDialogContentLabel, style: Theme.of(context) @@ -2336,6 +2581,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { borderSide: BorderSide( color: _pdfViewerThemeData!.passwordDialogStyle ?.inputFieldBorderColor ?? + _effectiveThemeData!.passwordDialogStyle + ?.inputFieldBorderColor ?? _themeData!.colorScheme.primary, )), errorBorder: OutlineInputBorder( @@ -2344,18 +2591,24 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { color: _pdfViewerThemeData! .passwordDialogStyle ?.errorBorderColor ?? + _effectiveThemeData!.passwordDialogStyle + ?.errorBorderColor ?? _themeData!.colorScheme.error, )), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: _pdfViewerThemeData!.passwordDialogStyle ?.inputFieldBorderColor ?? + _effectiveThemeData!.passwordDialogStyle + ?.inputFieldBorderColor ?? _themeData!.colorScheme.primary, )), focusedErrorBorder: OutlineInputBorder( borderSide: BorderSide( color: _pdfViewerThemeData! .passwordDialogStyle?.errorBorderColor ?? + _effectiveThemeData! + .passwordDialogStyle?.errorBorderColor ?? _themeData!.colorScheme.error, )), hintText: @@ -2372,8 +2625,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ) .merge(_pdfViewerThemeData!.passwordDialogStyle ?.inputFieldHintTextStyle), - labelText: - _localizations!.passwordDialogHintTextLabel, + labelText: isMaterial3 + ? null + : _localizations!.passwordDialogHintTextLabel, labelStyle: Theme.of(context) .textTheme .headlineMedium! @@ -2398,11 +2652,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { suffixIcon: IconButton( icon: Icon( _passwordVisible - ? Icons.visibility - : Icons.visibility_off, + ? (isMaterial3 + ? Icons.visibility_outlined + : Icons.visibility) + : (isMaterial3 + ? Icons.visibility_off_outlined + : Icons.visibility_off), color: _pdfViewerThemeData! .passwordDialogStyle ?.visibleIconColor ?? + _effectiveThemeData!.passwordDialogStyle + ?.visibleIconColor ?? Theme.of(context) .colorScheme .onSurface @@ -2459,6 +2719,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _textFieldController.clear(); Navigator.of(context).pop(); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text( _localizations!.pdfPasswordDialogCancelLabel, style: Theme.of(context) @@ -2466,6 +2733,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -2478,6 +2746,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { onPressed: () { _handlePasswordValidation(); }, + style: isMaterial3 + ? TextButton.styleFrom( + fixedSize: const Size(double.infinity, 40), + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 20), + ) + : null, child: Text( _localizations!.pdfPasswordDialogOpenLabel, style: Theme.of(context) @@ -2485,6 +2760,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .bodyMedium! .copyWith( fontSize: 14, + fontWeight: isMaterial3 ? FontWeight.w500 : null, color: _themeData!.colorScheme.primary, ) .merge(_pdfViewerThemeData! @@ -2567,6 +2843,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { xOffset: _pdfViewerController._horizontalOffset, yOffset: _pdfViewerController._verticalOffset); } + if (widget.initialPageNumber > 1 && + widget.initialPageNumber <= _pdfViewerController._totalPages) { + _pdfViewerController.jumpToPage(widget.initialPageNumber); + } _pdfViewerController._notifyPropertyChangedListeners( property: 'pageNavigate'); _pdfViewerController._notifyPropertyChangedListeners( @@ -2632,6 +2912,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Widget _getEmptyContainer() { return Container( color: _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (_themeData!.colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) : const Color(0xFF303030)), @@ -2645,10 +2926,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { LinearProgressIndicator( valueColor: AlwaysStoppedAnimation( _pdfViewerThemeData!.progressBarColor ?? + _effectiveThemeData!.progressBarColor ?? _themeData!.colorScheme.primary), - backgroundColor: _pdfViewerThemeData!.progressBarColor == null - ? _themeData!.colorScheme.primary.withOpacity(0.2) - : _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2), + backgroundColor: _pdfViewerThemeData!.progressBarColor != null + ? _pdfViewerThemeData!.progressBarColor!.withOpacity(0.2) + : _effectiveThemeData!.progressBarColor != null + ? _effectiveThemeData!.progressBarColor!.withOpacity(0.2) + : _themeData!.colorScheme.primary.withOpacity(0.2), ), ], ); @@ -2656,8 +2940,12 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - // call PdfViewerController methods after ScrollController attached. - _isDocumentLoaded(); + WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { + if (super.mounted && context.mounted) { + // call PdfViewerController methods after ScrollController attached. + _isDocumentLoaded(); + } + }); /// Find whether device is mobile or Laptop. _findDevice(context); @@ -2675,6 +2963,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { onPointerUp: _handlePointerUp, child: Container( color: _pdfViewerThemeData!.backgroundColor ?? + _effectiveThemeData!.backgroundColor ?? (_themeData!.colorScheme.brightness == Brightness.light ? const Color(0xFFD6D6D6) : const Color(0xFF303030)), @@ -2691,6 +2980,11 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { .findRenderObject()! // ignore: invalid_use_of_protected_member, avoid_as .constraints as BoxConstraints; + if (_viewportSize != _viewportConstraints.biggest) { + _tileImages.clear(); + _viewportSize = _viewportConstraints.biggest; + _getTileImage(); + } double totalHeight = 0.0; _isKeyPadRaised = View.of(context).viewInsets.bottom != 0.0; @@ -2828,6 +3122,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { widget.canShowPageLoadingIndicator, widget.canShowSignaturePadDialog, _handlePageTap, + _viewportPages, + _tileImages, _pdfViewerController._formFields, _pdfViewerController._annotations, _selectedAnnotation, @@ -2914,6 +3210,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { child: SinglePageView( _singlePageViewKey, _pdfViewerController, + _transformationController, _pageController, _handleSinglePageViewPageChanged, _interactionUpdate, @@ -2934,6 +3231,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _textDirection, _isTablet, _scrollDirection, + _getTileImage, children), ); if (_isSinglePageViewPageChanged && @@ -2996,6 +3294,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { child: child), ); pdfContainer = PdfScrollable( + _transformationController, widget.canShowPaginationDialog, widget.canShowScrollStatus, widget.canShowScrollHead, @@ -3018,6 +3317,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { isBookmarkViewOpen, _textDirection, child, + _getTileImage, key: _pdfScrollableStateKey, onDoubleTap: _handleDoubleTap, ); @@ -3554,6 +3854,156 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } + Future _getTileImage() async { + _tileImages.clear(); + _viewportPages.clear(); + _tileTimer?.cancel(); + final double zoomLevel = _transformationController.value[0]; + if (zoomLevel > 1.75 && + (kIsWeb || !Platform.environment.containsKey('FLUTTER_TEST'))) { + _tileTimer = Timer(const Duration(milliseconds: 500), () async { + final Offset currentOffset = + _transformationController.toScene(Offset.zero) * zoomLevel; + if (_pageLayoutMode == PdfPageLayoutMode.continuous) { + for (int pageNumber = _pdfViewerController.pageNumber - 1; + pageNumber <= _pdfViewerController.pageNumber + 1; + pageNumber++) { + await _getSpecificTile(pageNumber, currentOffset, zoomLevel); + } + } else { + await _getSpecificTile( + _pdfViewerController.pageNumber, currentOffset, zoomLevel); + } + _checkMount(); + }); + } + } + + Future _getSpecificTile( + int pageNumber, Offset offset, double zoomLevel) async { + if (pageNumber >= 1 && pageNumber <= _pdfViewerController.pageCount) { + final PdfPageRotateAngle rotatedAngle = + _document!.pages[pageNumber - 1].rotation; + + final bool isRotatedTo90or270 = + rotatedAngle == PdfPageRotateAngle.rotateAngle90 || + rotatedAngle == PdfPageRotateAngle.rotateAngle270; + final Size originalPageSize = _document!.pages[pageNumber - 1].size; + final double heightPercentage = (isRotatedTo90or270 + ? originalPageSize.width + : originalPageSize.height) / + _pdfPages[pageNumber]!.pageSize.height; + + final double x = offset.dx * heightPercentage; + final double y = offset.dy * heightPercentage; + + final Rect viewportRect = Rect.fromLTWH( + x, + y, + _viewportSize.width * heightPercentage, + _viewportSize.height * heightPercentage); + + Rect pageBounds = Rect.zero; + if (_pageLayoutMode == PdfPageLayoutMode.continuous) { + if (_scrollDirection == PdfScrollDirection.vertical) { + pageBounds = Rect.fromLTWH( + 0, + _pdfPages[pageNumber]!.pageOffset * zoomLevel * heightPercentage, + _pdfPages[pageNumber]!.pageSize.width * + zoomLevel * + heightPercentage, + _pdfPages[pageNumber]!.pageSize.height * + zoomLevel * + heightPercentage); + } else { + pageBounds = Rect.fromLTWH( + _pdfPages[pageNumber]!.pageOffset * zoomLevel * heightPercentage, + 0, + _pdfPages[pageNumber]!.pageSize.width * + zoomLevel * + heightPercentage, + _pdfPages[pageNumber]!.pageSize.height * + zoomLevel * + heightPercentage); + } + } else { + final double pageY = + (_viewportHeight - _pdfPages[pageNumber]!.pageSize.height).abs() / + 2; + pageBounds = Rect.fromLTWH( + 0, + pageY * zoomLevel * heightPercentage, + _pdfPages[pageNumber]!.pageSize.width * + zoomLevel * + heightPercentage, + _pdfPages[pageNumber]!.pageSize.height * + zoomLevel * + heightPercentage); + } + + Rect exposed = pageBounds.intersect(viewportRect); + if (!exposed.isEmpty) { + if (_pageLayoutMode == PdfPageLayoutMode.continuous) { + if (_scrollDirection == PdfScrollDirection.vertical) { + exposed = exposed.translate( + 0, + -(_pdfPages[pageNumber]!.pageOffset * + zoomLevel * + heightPercentage)); + } else { + exposed = exposed.translate( + -(_pdfPages[pageNumber]!.pageOffset * + zoomLevel * + heightPercentage), + 0); + } + } else { + final double verticalGreyAreaSize = + (_pdfPages[pageNumber]!.pageSize.height - _viewportSize.height) + .abs() / + 2; + final double horizontalGreyAreaSize = _deviceOrientation == + Orientation.portrait + ? (_pdfPages[pageNumber]!.pageSize.width - _viewportSize.width) + .abs() / + 2 + : 0; + + exposed = exposed.translate( + -(horizontalGreyAreaSize * zoomLevel * heightPercentage), + -(verticalGreyAreaSize * zoomLevel * heightPercentage)); + } + _viewportPages[pageNumber] = Rect.fromLTWH( + exposed.left / zoomLevel, + exposed.top / zoomLevel, + exposed.width / zoomLevel, + exposed.height / zoomLevel); + + double ratio = + _pdfPages[pageNumber]!.pageSize.width / originalPageSize.width; + ratio = ratio < 1 ? 1 : ratio; + final double dpr = MediaQuery.of(context).devicePixelRatio; + + if (zoomLevel == _transformationController.value[0]) { + final Uint8List? tileImage = await _plugin.getTileImage( + pageNumber, + zoomLevel * dpr * ratio, + exposed.left / zoomLevel, + exposed.top / zoomLevel, + exposed.width * dpr * ratio, + exposed.height * dpr * ratio, + ); + + if (tileImage != null && + zoomLevel == _transformationController.value[0]) { + _tileImages[pageNumber] = tileImage; + _pdfPagesKey[pageNumber]?.currentState?.rebuild(); + } + } + } + } + } + // Checks whether the current Widget is mounted and then relayout the Widget. void _checkMount() { if (super.mounted) { @@ -3808,9 +4258,9 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { true && _layoutChangeOffset == Offset.zero; }); } - _tempScrollDirection = _scrollDirection; - _pageLayoutMode = widget.pageLayoutMode; } + _tempScrollDirection = _scrollDirection; + _pageLayoutMode = widget.pageLayoutMode; } else if (widget.pageLayoutMode == PdfPageLayoutMode.continuous || widget.pageLayoutMode != PdfPageLayoutMode.single) { _pageOffsetBeforeScrollDirectionChange = @@ -4141,6 +4591,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfScrollableStateKey.currentState ?.jumpTo(yOffset: _pdfPages[pageNumber]!.pageOffset); } + _getTileImage(); } /// Jump to the bookmark location. @@ -4218,6 +4669,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfScrollableStateKey.currentState ?.jumpTo(xOffset: xOffset, yOffset: yOffset); } + _getTileImage(); } } @@ -4298,6 +4750,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { PageStorage.of(context).writeState( context, _pdfViewerController.zoomLevel, identifier: 'zoomLevel_${widget.key}'); + _getTileImage(); } } else { if (_singlePageViewKey.currentState != null) { @@ -4326,6 +4779,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { PageStorage.of(context).writeState( context, _pdfViewerController.zoomLevel, identifier: 'zoomLevel_${widget.key}'); + _getTileImage(); } } } else if (property == 'clearTextSelection') { @@ -4348,6 +4802,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { xOffset: _pdfViewerController._horizontalOffset, yOffset: _pdfViewerController._verticalOffset); } + _getTileImage(); } else if (property == 'pageNavigate' && _pdfViewerController._pageNavigator != null) { _clearSelection(); @@ -4421,28 +4876,51 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } } else if (property == 'clearFormData') { + final List formFieldValueChangeRecords = + []; for (final PdfFormField field in _pdfViewerController._formFields) { - final PdfFormFieldHelper formFieldHelper = - PdfFormFieldHelper.getHelper(field); - final int formFieldPageNumber = formFieldHelper.pageIndex + 1; - if (_pdfViewerController._clearFormDataPageNumber == 0 || - formFieldPageNumber == - _pdfViewerController._clearFormDataPageNumber) { - if (field is PdfTextFormField) { - field.text = ''; - } else if (field is PdfCheckboxFormField) { - field.isChecked = false; - } else if (field is PdfRadioFormField) { - field.selectedItem = field.items[0]; - } else if (field is PdfComboBoxFormField) { - field.selectedItem = field.items[0]; - } else if (field is PdfListBoxFormField) { - field.selectedItems = null; - } else if (field is PdfSignatureFormField) { - field.signature = null; + if (!field.readOnly) { + final PdfFormFieldHelper formFieldHelper = + PdfFormFieldHelper.getHelper(field); + final int formFieldPageNumber = formFieldHelper.pageIndex + 1; + if (_pdfViewerController._clearFormDataPageNumber == 0 || + formFieldPageNumber == + _pdfViewerController._clearFormDataPageNumber) { + FormFieldValueChangeRecord? record; + if (formFieldHelper is PdfTextFormFieldHelper && + field is PdfTextFormField) { + record = _updateFormField(field, ''); + } else if (formFieldHelper is PdfCheckboxFormFieldHelper && + field is PdfCheckboxFormField) { + record = _updateFormField(field, false); + } else if (formFieldHelper is PdfRadioFormFieldHelper && + field is PdfRadioFormField) { + if (formFieldHelper.canReset) { + record = _updateFormField(field, ''); + } else { + record = _updateFormField(field, field.items[0]); + } + } else if (formFieldHelper is PdfComboBoxFormFieldHelper && + field is PdfComboBoxFormField) { + record = _updateFormField(field, field.items[0]); + } else if (formFieldHelper is PdfListBoxFormFieldHelper && + field is PdfListBoxFormField) { + record = _updateFormField(field, List.empty()); + } else if (formFieldHelper is PdfSignatureFormFieldHelper && + field is PdfSignatureFormField) { + record = _updateFormField(field, null); + } + if (record != null) { + formFieldValueChangeRecords.add(record); + } } } } + if (formFieldValueChangeRecords.isNotEmpty) { + _changeTracker.addChange(FormFieldValueChangeTracker( + records: formFieldValueChangeRecords, + onUndoOrRedo: _updateFormField)); + } } else if (property == 'addAnnotation') { if (_pdfViewerController._annotation != null) { final Annotation newAnnotation = _pdfViewerController._annotation!; @@ -4885,6 +5363,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_isPageChanged) { _isPageChanged = false; } + _getTileImage(); }); } else { if (_pdfScrollableStateKey.currentState != null && @@ -4895,6 +5374,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (_isPageChanged) { _isPageChanged = false; } + _getTileImage(); }); } } diff --git a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml index 2e120ba42..8c49ba7d1 100644 --- a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_pdfviewer description: Flutter PDF Viewer library is used to display a PDF document seamlessly and efficiently. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_pdfviewer environment: @@ -38,29 +38,29 @@ dependencies: http: ^1.0.0 uuid: ^4.1.0 device_info_plus: ^9.0.2 - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' syncfusion_pdfviewer_platform_interface: path: ../syncfusion_pdfviewer_platform_interface - + syncfusion_pdfviewer_web: path: ../syncfusion_pdfviewer_web - + syncfusion_pdfviewer_macos: path: ../syncfusion_pdfviewer_macos - + syncfusion_pdfviewer_windows: path: ../syncfusion_pdfviewer_windows - + syncfusion_flutter_core: path: ../syncfusion_flutter_core syncfusion_flutter_pdf: path: ../syncfusion_flutter_pdf - + syncfusion_flutter_signaturepad: path: ../syncfusion_flutter_signaturepad - + url_launcher: ^6.1.0 dev_dependencies: diff --git a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md index 4f6863653..d8c81fb5c 100644 --- a/packages/syncfusion_flutter_signaturepad/CHANGELOG.md +++ b/packages/syncfusion_flutter_signaturepad/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +**General** + +* Provided th​e Material 3 themes support. + ## [21.1.35] - 03/23/2023 **Bugs** diff --git a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart index 2e4c0c18b..52279f860 100644 --- a/packages/syncfusion_flutter_signaturepad/example/lib/main.dart +++ b/packages/syncfusion_flutter_signaturepad/example/lib/main.dart @@ -11,9 +11,6 @@ class SignaturePadApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData( - useMaterial3: false, - ), title: 'SfSignaturePad Demo', home: _MyHomePage(), ); diff --git a/packages/syncfusion_flutter_signaturepad/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/pubspec.yaml index 990ad513b..98fe5109b 100644 --- a/packages/syncfusion_flutter_signaturepad/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_signaturepad description: The Flutter Signature Pad widget allows you to capture smooth and more realistic signatures through drawn gestures and save it as an image. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_signaturepad environment: diff --git a/packages/syncfusion_flutter_sliders/CHANGELOG.md b/packages/syncfusion_flutter_sliders/CHANGELOG.md index bbeaebc2a..fc1ec0a58 100644 --- a/packages/syncfusion_flutter_sliders/CHANGELOG.md +++ b/packages/syncfusion_flutter_sliders/CHANGELOG.md @@ -1,5 +1,15 @@ ## Unreleased +**General** + +* Provided th​e Material 3 themes support. + +## [24.1.46] - 17/01/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + ## Range Slider **Bugs** diff --git a/packages/syncfusion_flutter_sliders/example/lib/main.dart b/packages/syncfusion_flutter_sliders/example/lib/main.dart index 587a3ea47..b5803fbb8 100644 --- a/packages/syncfusion_flutter_sliders/example/lib/main.dart +++ b/packages/syncfusion_flutter_sliders/example/lib/main.dart @@ -11,12 +11,9 @@ void main() { class RangeSelectorApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Range Selector Demo', - theme: ThemeData( - useMaterial3: false, - ), - home: const MyHomePage(), + home: MyHomePage(), ); } } diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart index 195d31c64..6864bd845 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart @@ -12,6 +12,7 @@ import 'common.dart'; import 'constants.dart'; import 'range_slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design range selector. /// @@ -1527,6 +1528,15 @@ class _SfRangeSelectorState extends State SfRangeSelectorThemeData _getRangeSelectorThemeData(ThemeData themeData) { SfRangeSelectorThemeData rangeSelectorThemeData = SfRangeSelectorTheme.of(context)!; + final bool isMaterial3 = themeData.useMaterial3; + final SfRangeSelectorThemeData effectiveThemeData = isMaterial3 + ? SfRangeSelectorThemeDataM3(context) + : SfRangeSelectorThemeDataM2(context); + final Color labelColor = isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : widget.enabled + ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) + : themeData.colorScheme.onSurface.withOpacity(0.32); final double minTrackHeight = math.min( rangeSelectorThemeData.activeTrackHeight, rangeSelectorThemeData.inactiveTrackHeight); @@ -1541,72 +1551,71 @@ class _SfRangeSelectorState extends State tickOffset: rangeSelectorThemeData.tickOffset, labelOffset: rangeSelectorThemeData.labelOffset ?? (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0)), - inactiveLabelStyle: rangeSelectorThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: widget.enabled - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: rangeSelectorThemeData.activeLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: widget.enabled - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: rangeSelectorThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyLarge! - .copyWith(color: themeData.colorScheme.surface), + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSelectorThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSelectorThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface) + .merge(rangeSelectorThemeData.tooltipTextStyle), inactiveTrackColor: widget.inactiveColor ?? rangeSelectorThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), + effectiveThemeData.inactiveTrackColor, activeTrackColor: widget.activeColor ?? rangeSelectorThemeData.activeTrackColor ?? - themeData.colorScheme.primary, + effectiveThemeData.activeTrackColor, thumbColor: widget.activeColor ?? rangeSelectorThemeData.thumbColor ?? - themeData.colorScheme.primary, + effectiveThemeData.thumbColor, activeTickColor: rangeSelectorThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.activeTickColor, inactiveTickColor: rangeSelectorThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.inactiveTickColor, disabledActiveTickColor: rangeSelectorThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveTickColor, disabledInactiveTickColor: rangeSelectorThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveTickColor, activeMinorTickColor: rangeSelectorThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.activeMinorTickColor, inactiveMinorTickColor: rangeSelectorThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.inactiveMinorTickColor, disabledActiveMinorTickColor: rangeSelectorThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, // ignore: lines_longer_than_80_chars disabledInactiveMinorTickColor: rangeSelectorThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveMinorTickColor, overlayColor: widget.activeColor?.withOpacity(0.12) ?? rangeSelectorThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), + effectiveThemeData.overlayColor, inactiveDividerColor: widget.activeColor ?? rangeSelectorThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), + effectiveThemeData.inactiveDividerColor, activeDividerColor: widget.inactiveColor ?? rangeSelectorThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: rangeSelectorThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveDividerColor, disabledActiveDividerColor: rangeSelectorThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), + effectiveThemeData.disabledActiveDividerColor, disabledActiveTrackColor: rangeSelectorThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), + effectiveThemeData.disabledActiveTrackColor, disabledInactiveTrackColor: rangeSelectorThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveTrackColor, disabledThumbColor: rangeSelectorThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), + effectiveThemeData.disabledThumbColor, thumbStrokeColor: rangeSelectorThemeData.thumbStrokeColor, overlappingThumbStrokeColor: rangeSelectorThemeData.overlappingThumbStrokeColor ?? @@ -1617,17 +1626,13 @@ class _SfRangeSelectorState extends State overlappingTooltipStrokeColor: rangeSelectorThemeData.overlappingTooltipStrokeColor ?? themeData.colorScheme.surface, - activeRegionColor: - rangeSelectorThemeData.activeRegionColor ?? Colors.transparent, + activeRegionColor: rangeSelectorThemeData.activeRegionColor ?? + effectiveThemeData.activeRegionColor, inactiveRegionColor: widget.inactiveColor ?? rangeSelectorThemeData.inactiveRegionColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1).withOpacity(0.75) - : const Color.fromRGBO(48, 48, 48, 1).withOpacity(0.75)), + effectiveThemeData.inactiveRegionColor, tooltipBackgroundColor: rangeSelectorThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.tooltipBackgroundColor, trackCornerRadius: rangeSelectorThemeData.trackCornerRadius ?? maxTrackHeight / 2, thumbRadius: rangeSelectorThemeData.thumbRadius, @@ -2590,6 +2595,8 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter); final Rect rightRect = Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight); + startSemanticsNode ??= SemanticsNode(); + endSemanticsNode ??= SemanticsNode(); switch (textDirection) { case TextDirection.ltr: startSemanticsNode!.rect = leftRect; diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart index 6648f1ba6..904cd8175 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart @@ -11,6 +11,7 @@ import 'common.dart'; import 'constants.dart'; import 'range_slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design range slider. /// @@ -1416,6 +1417,15 @@ class _SfRangeSliderState extends State ThemeData themeData, bool isActive) { SfRangeSliderThemeData rangeSliderThemeData = SfRangeSliderTheme.of(context)!; + final bool isMaterial3 = themeData.useMaterial3; + final SfRangeSliderThemeData effectiveThemeData = isMaterial3 + ? SfRangeSliderThemeDataM3(context) + : SfRangeSliderThemeDataM2(context); + final Color labelColor = isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : isActive + ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) + : themeData.colorScheme.onSurface.withOpacity(0.32); final double minTrackHeight = math.min( rangeSliderThemeData.activeTrackHeight, rangeSliderThemeData.inactiveTrackHeight); @@ -1426,75 +1436,72 @@ class _SfRangeSliderState extends State activeTrackHeight: rangeSliderThemeData.activeTrackHeight, inactiveTrackHeight: rangeSliderThemeData.inactiveTrackHeight, tickOffset: rangeSliderThemeData.tickOffset, - inactiveLabelStyle: rangeSliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: isActive - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: rangeSliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: isActive - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: rangeSliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyLarge! - .copyWith(color: themeData.colorScheme.surface), + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSliderThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(rangeSliderThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface) + .merge(rangeSliderThemeData.tooltipTextStyle), inactiveTrackColor: widget.inactiveColor ?? rangeSliderThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), + effectiveThemeData.inactiveTrackColor, activeTrackColor: widget.activeColor ?? rangeSliderThemeData.activeTrackColor ?? - themeData.colorScheme.primary, + effectiveThemeData.activeTrackColor, thumbColor: widget.activeColor ?? rangeSliderThemeData.thumbColor ?? - themeData.colorScheme.primary, + effectiveThemeData.thumbColor, activeTickColor: rangeSliderThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.activeTickColor, inactiveTickColor: rangeSliderThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.inactiveTickColor, disabledActiveTickColor: rangeSliderThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveTickColor, disabledInactiveTickColor: rangeSliderThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveTickColor, activeMinorTickColor: rangeSliderThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.activeMinorTickColor, inactiveMinorTickColor: rangeSliderThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.disabledInactiveMinorTickColor, disabledActiveMinorTickColor: rangeSliderThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, disabledInactiveMinorTickColor: rangeSliderThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveMinorTickColor, // ignore: lines_longer_than_80_chars overlayColor: widget.activeColor?.withOpacity(0.12) ?? rangeSliderThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), + effectiveThemeData.overlayColor, inactiveDividerColor: widget.activeColor ?? rangeSliderThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), + effectiveThemeData.inactiveDividerColor, activeDividerColor: widget.inactiveColor ?? rangeSliderThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: rangeSliderThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveDividerColor, disabledActiveDividerColor: rangeSliderThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), + effectiveThemeData.disabledActiveDividerColor, disabledActiveTrackColor: rangeSliderThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), + effectiveThemeData.disabledActiveTrackColor, disabledInactiveTrackColor: rangeSliderThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveTrackColor, disabledThumbColor: rangeSliderThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), + effectiveThemeData.disabledThumbColor, tooltipBackgroundColor: rangeSliderThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.tooltipBackgroundColor, thumbStrokeColor: rangeSliderThemeData.thumbStrokeColor, overlappingThumbStrokeColor: rangeSliderThemeData.overlappingThumbStrokeColor ?? @@ -2235,6 +2242,8 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { final Rect endRect = sliderType == SliderType.horizontal ? Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight) : Rect.fromPoints(node.rect.centerLeft, node.rect.topRight); + startSemanticsNode ??= SemanticsNode(); + endSemanticsNode ??= SemanticsNode(); if (sliderType == SliderType.vertical || textDirection == TextDirection.ltr) { startSemanticsNode!.rect = startRect; diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart index 3478f6195..aabc09886 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart @@ -138,6 +138,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _valuesInMilliseconds = SfRangeValues( values.start.millisecondsSinceEpoch.toDouble(), values.end.millisecondsSinceEpoch.toDouble()); + } else { + _values = SfRangeValues( + (values.start as num).toDouble(), (values.end as num).toDouble()); } unformattedLabels = []; updateTextPainter(); @@ -192,7 +195,10 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (_values == values) { return; } - _values = values; + _values = isDateTime + ? values + : SfRangeValues( + (values.start as num).toDouble(), (values.end as num).toDouble()); if (isDateTime) { _valuesInMilliseconds = SfRangeValues( _values.start.millisecondsSinceEpoch.toDouble(), diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider.dart b/packages/syncfusion_flutter_sliders/lib/src/slider.dart index c9b09fecb..85deced21 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider.dart @@ -15,6 +15,7 @@ import 'common.dart'; import 'constants.dart'; import 'slider_base.dart'; import 'slider_shapes.dart'; +import 'theme.dart'; /// A Material Design slider. /// @@ -1264,6 +1265,17 @@ class _SfSliderState extends State with TickerProviderStateMixin { SfSliderThemeData _getSliderThemeData(ThemeData themeData, bool isActive) { SfSliderThemeData sliderThemeData = SfSliderTheme.of(context); + + ///An instance for material 2 and material 3 classes + final SfSliderThemeData effectiveThemeData = themeData.useMaterial3 + ? SfSliderThemeDataM3(context) + : SfSliderThemeDataM2(context); + final bool isMaterial3 = themeData.useMaterial3; + final Color labelColor = isMaterial3 + ? themeData.colorScheme.onSurfaceVariant + : isActive + ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) + : themeData.colorScheme.onSurface.withOpacity(0.32); final double minTrackHeight = math.min( sliderThemeData.activeTrackHeight, sliderThemeData.inactiveTrackHeight); final double maxTrackHeight = math.max( @@ -1272,72 +1284,68 @@ class _SfSliderState extends State with TickerProviderStateMixin { activeTrackHeight: sliderThemeData.activeTrackHeight, inactiveTrackHeight: sliderThemeData.inactiveTrackHeight, tickOffset: sliderThemeData.tickOffset, - inactiveLabelStyle: sliderThemeData.inactiveLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: isActive - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - activeLabelStyle: sliderThemeData.activeLabelStyle ?? - themeData.textTheme.bodyLarge!.copyWith( - color: isActive - ? themeData.textTheme.bodyLarge!.color!.withOpacity(0.87) - : themeData.colorScheme.onSurface.withOpacity(0.32)), - tooltipTextStyle: sliderThemeData.tooltipTextStyle ?? - themeData.textTheme.bodyLarge! - .copyWith(color: themeData.colorScheme.surface), + inactiveLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(sliderThemeData.inactiveLabelStyle), + activeLabelStyle: themeData.textTheme.bodyLarge! + .copyWith(color: labelColor, fontSize: isMaterial3 ? 12 : 14) + .merge(sliderThemeData.activeLabelStyle), + tooltipTextStyle: themeData.textTheme.bodyLarge! + .copyWith( + fontSize: isMaterial3 ? 12 : 14, + color: isMaterial3 + ? themeData.colorScheme.onPrimary + : themeData.colorScheme.surface) + .merge(sliderThemeData.tooltipTextStyle), inactiveTrackColor: widget.inactiveColor ?? sliderThemeData.inactiveTrackColor ?? - themeData.colorScheme.primary.withOpacity(0.24), + effectiveThemeData.inactiveTrackColor, activeTrackColor: widget.activeColor ?? sliderThemeData.activeTrackColor ?? - themeData.colorScheme.primary, + effectiveThemeData.activeTrackColor, thumbColor: widget.activeColor ?? sliderThemeData.thumbColor ?? - themeData.colorScheme.primary, - activeTickColor: sliderThemeData.activeTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.thumbColor, + activeTickColor: + sliderThemeData.activeTickColor ?? effectiveThemeData.activeTickColor, inactiveTickColor: sliderThemeData.inactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.inactiveTickColor, disabledActiveTickColor: sliderThemeData.disabledActiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveTickColor, disabledInactiveTickColor: sliderThemeData.disabledInactiveTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledInactiveTickColor, activeMinorTickColor: sliderThemeData.activeMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.activeMinorTickColor, inactiveMinorTickColor: sliderThemeData.inactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.37), + effectiveThemeData.inactiveMinorTickColor, disabledActiveMinorTickColor: sliderThemeData.disabledActiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), + effectiveThemeData.disabledActiveMinorTickColor, disabledInactiveMinorTickColor: sliderThemeData.disabledInactiveMinorTickColor ?? - themeData.colorScheme.onSurface.withOpacity(0.24), - // ignore: lines_longer_than_80_chars + effectiveThemeData.disabledInactiveMinorTickColor, overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderThemeData.overlayColor ?? - themeData.colorScheme.primary.withOpacity(0.12), + effectiveThemeData.overlayColor, inactiveDividerColor: widget.activeColor ?? sliderThemeData.inactiveDividerColor ?? - themeData.colorScheme.primary.withOpacity(0.54), + effectiveThemeData.inactiveDividerColor, activeDividerColor: widget.inactiveColor ?? sliderThemeData.activeDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.54), + effectiveThemeData.activeDividerColor, disabledInactiveDividerColor: sliderThemeData.disabledInactiveDividerColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveDividerColor, disabledActiveDividerColor: sliderThemeData.disabledActiveDividerColor ?? - themeData.colorScheme.onPrimary.withOpacity(0.12), + effectiveThemeData.disabledActiveDividerColor, disabledActiveTrackColor: sliderThemeData.disabledActiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.32), + effectiveThemeData.disabledActiveTrackColor, disabledInactiveTrackColor: sliderThemeData.disabledInactiveTrackColor ?? - themeData.colorScheme.onSurface.withOpacity(0.12), + effectiveThemeData.disabledInactiveTrackColor, disabledThumbColor: sliderThemeData.disabledThumbColor ?? - Color.alphaBlend(themeData.colorScheme.onSurface.withOpacity(0.38), - themeData.colorScheme.surface), + effectiveThemeData.disabledThumbColor, tooltipBackgroundColor: sliderThemeData.tooltipBackgroundColor ?? - (themeData.brightness == Brightness.light - ? const Color.fromRGBO(97, 97, 97, 1) - : const Color.fromRGBO(224, 224, 224, 1)), + effectiveThemeData.tooltipBackgroundColor, thumbStrokeColor: sliderThemeData.thumbStrokeColor, activeDividerStrokeColor: sliderThemeData.activeDividerStrokeColor, inactiveDividerStrokeColor: sliderThemeData.inactiveDividerStrokeColor, diff --git a/packages/syncfusion_flutter_sliders/pubspec.yaml b/packages/syncfusion_flutter_sliders/pubspec.yaml index c574f41e2..5b18c264d 100644 --- a/packages/syncfusion_flutter_sliders/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_sliders description: A Flutter Sliders library for creating highly customizable and UI-rich slider, range slider, and range selector widgets for filtering purposes. -version: 23.2.5 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_sliders environment: @@ -10,11 +10,11 @@ dependencies: flutter: sdk: flutter - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' syncfusion_flutter_core: path: ../syncfusion_flutter_core - + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/syncfusion_flutter_treemap/CHANGELOG.md b/packages/syncfusion_flutter_treemap/CHANGELOG.md index df968a328..9b9af8c8a 100644 --- a/packages/syncfusion_flutter_treemap/CHANGELOG.md +++ b/packages/syncfusion_flutter_treemap/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +**General** + +* Provided th​e Material 3 themes support. + ## [20.2.36] - 07/01/2022 **Features** diff --git a/packages/syncfusion_flutter_treemap/example/lib/main.dart b/packages/syncfusion_flutter_treemap/example/lib/main.dart index 16f8b7c45..8406dfa85 100644 --- a/packages/syncfusion_flutter_treemap/example/lib/main.dart +++ b/packages/syncfusion_flutter_treemap/example/lib/main.dart @@ -9,12 +9,9 @@ void main() { class TreemapApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( title: 'Treemap Demo', - theme: ThemeData( - useMaterial3: false, - ), - home: const MyHomePage(), + home: MyHomePage(), ); } } diff --git a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart index b311d4300..57678ec13 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart @@ -824,8 +824,9 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { // To place the pointer at the center of the segment in case of // solid bar legend type. normalized = 0.5; - } else + } else { normalized = value / (length - 1); + } } } return Offset(normalized, normalized); diff --git a/packages/syncfusion_flutter_treemap/pubspec.yaml b/packages/syncfusion_flutter_treemap/pubspec.yaml index 97b909b5c..052bdb583 100644 --- a/packages/syncfusion_flutter_treemap/pubspec.yaml +++ b/packages/syncfusion_flutter_treemap/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_treemap description: A Flutter Treemap library for creating interactive treemap to visualize flat and hierarchical data based on squarified, slice, and dice algorithms. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_treemap environment: diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart index ebbc76b58..1cc8c9ec1 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/workbook.dart @@ -6786,7 +6786,6 @@ class Workbook { /// Dispose objects. void dispose() { if (_archives != null) { - _archives!.files.clear(); _archives = null; } diff --git a/packages/syncfusion_flutter_xlsio/pubspec.yaml b/packages/syncfusion_flutter_xlsio/pubspec.yaml index f422a8e85..710a43daa 100644 --- a/packages/syncfusion_flutter_xlsio/pubspec.yaml +++ b/packages/syncfusion_flutter_xlsio/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_flutter_xlsio description: Flutter XlsIO is a Dart library for creating Excel documents with formulas, charts, images, hyperlinks, autofit rows and columns, and more. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_xlsio environment: @@ -12,12 +12,12 @@ dependencies: xml: ">=5.1.0 <7.0.0" archive: ">=3.1.2 <4.0.0" image: ">=3.0.1 <5.0.0" - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' crypto: ">=3.0.0 <4.0.0" jiffy: ">=6.1.0 <7.0.0" syncfusion_officecore: path: ../syncfusion_officecore - + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/syncfusion_localizations/CHANGELOG.md b/packages/syncfusion_localizations/CHANGELOG.md index d3c0959fe..a82beadf7 100644 --- a/packages/syncfusion_localizations/CHANGELOG.md +++ b/packages/syncfusion_localizations/CHANGELOG.md @@ -1,3 +1,9 @@ +## [24.1.46] - 17/01/2024 + +**General** + +* Upgraded the `intl` package to the latest version 0.19.0. + ## [19.4.38+1] - 12/20/2021 * Added localization for `rowsPerPageDataPagerLabel` property. diff --git a/packages/syncfusion_localizations/example/lib/main.dart b/packages/syncfusion_localizations/example/lib/main.dart index ec1c33287..1b8009362 100644 --- a/packages/syncfusion_localizations/example/lib/main.dart +++ b/packages/syncfusion_localizations/example/lib/main.dart @@ -12,9 +12,6 @@ class CalendarApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData( - useMaterial3: false, - ), title: 'Calendar Demo', //ignore: always_specify_types localizationsDelegates: [ diff --git a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart index f14351dde..a65265579 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart +++ b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart @@ -4003,7 +4003,7 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get pdfHyperlinkDialogCancelLabel => r'ABBRECHEN'; @override - String get pdfHyperlinkDialogOpenLabel => r'OFFEN'; + String get pdfHyperlinkDialogOpenLabel => r'ÖFFNEN'; @override String get pdfHyperlinkLabel => r'Webseite öffnen'; @@ -4025,7 +4025,7 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get pdfPasswordDialogCancelLabel => r'ABBRECHEN'; @override - String get pdfPasswordDialogOpenLabel => r'OFFEN'; + String get pdfPasswordDialogOpenLabel => r'ÖFFNEN'; @override String get pdfScrollStatusOfLabel => r'von'; diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb index 465c234a5..8e011053e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb @@ -16,7 +16,7 @@ "passwordDialogContentLabel" : "Geben Sie das Passwort ein, um diese PDF-Datei zu öffnen", "passwordDialogHintTextLabel" : "Passwort eingeben", "passwordDialogInvalidPasswordLabel" : "Ungültiges Passwort", -"pdfPasswordDialogOpenLabel" : "OFFEN", +"pdfPasswordDialogOpenLabel" : "ÖFFNEN", "pdfPasswordDialogCancelLabel" : "ABBRECHEN", "pdfSignaturePadDialogHeaderTextLabel" : "Zeichnen Sie Ihre Unterschrift", "pdfSignaturePadDialogPenColorLabel" : "Stiftfarbe", @@ -67,7 +67,7 @@ "series" : "Serie", "pdfHyperlinkLabel" : "Webseite öffnen", "pdfHyperlinkContentLabel" : "Möchten Sie die Seite öffnen unter", -"pdfHyperlinkDialogOpenLabel" : "OFFEN", +"pdfHyperlinkDialogOpenLabel" : "ÖFFNEN", "pdfHyperlinkDialogCancelLabel" : "ABBRECHEN", "afterDataGridFilteringLabel" : "Nach", "afterOrEqualDataGridFilteringLabel" : "Nach oder gleich", diff --git a/packages/syncfusion_localizations/pubspec.yaml b/packages/syncfusion_localizations/pubspec.yaml index 49e917780..b75f2ec53 100644 --- a/packages/syncfusion_localizations/pubspec.yaml +++ b/packages/syncfusion_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_localizations description: Syncfusion Localizations package contains localized text for 77 cultures for all the applicable Syncfusion Flutter Widgets. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_localizations environment: @@ -9,7 +9,7 @@ environment: dependencies: flutter: sdk: flutter - intl: ^0.18.0 + intl: '>=0.18.1 <0.20.0' syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_officechart/pubspec.yaml b/packages/syncfusion_officechart/pubspec.yaml index ba3a5b360..6e370829a 100644 --- a/packages/syncfusion_officechart/pubspec.yaml +++ b/packages/syncfusion_officechart/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_officechart description: Syncfusion Flutter Office Chart is a library written natively in Dart for creating Office charts from scratch. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_officechart environment: @@ -13,7 +13,7 @@ dependencies: archive: ">=3.1.2 <4.0.0" syncfusion_flutter_xlsio: path: ../syncfusion_flutter_xlsio - + dev_dependencies: flutter_test: sdk: flutter diff --git a/packages/syncfusion_officecore/lib/officecore.dart b/packages/syncfusion_officecore/lib/officecore.dart index b1a3a4a19..11baf04c2 100644 --- a/packages/syncfusion_officecore/lib/officecore.dart +++ b/packages/syncfusion_officecore/lib/officecore.dart @@ -1,3 +1,3 @@ library officecore; -part 'src/built_in_properties.dart'; +export 'src/built_in_properties.dart' show BuiltInProperties; diff --git a/packages/syncfusion_officecore/lib/src/built_in_properties.dart b/packages/syncfusion_officecore/lib/src/built_in_properties.dart index fdfc0fa82..b1b9aaca1 100644 --- a/packages/syncfusion_officecore/lib/src/built_in_properties.dart +++ b/packages/syncfusion_officecore/lib/src/built_in_properties.dart @@ -1,5 +1,3 @@ -part of officecore; - ///Represent the document properties in a MS Excel Document. class BuiltInProperties { /// Gets or Sets author of the document. diff --git a/packages/syncfusion_officecore/pubspec.yaml b/packages/syncfusion_officecore/pubspec.yaml index bc13177c2..2d3b6bd1a 100644 --- a/packages/syncfusion_officecore/pubspec.yaml +++ b/packages/syncfusion_officecore/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_officecore description: Syncfusion Flutter Office Core is a dependant library for Syncfusion Flutter XlsIO, written natively in Dart for creating Excel from scratch. -version: 24.1.41 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_officecore environment: diff --git a/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml b/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml index 6fc3c8a4f..4654665de 100644 --- a/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_macos/example/pubspec.yaml @@ -14,10 +14,10 @@ dependencies: syncfusion_flutter_pdfviewer: git: - url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer + url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: release/24.1.1 - ref: release/24.1.1 + branch: release/25.1.1 + ref: release/25.1.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift index f65ad6789..8e4244524 100644 --- a/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_pdfviewer_macos/macos/Classes/SyncfusionFlutterPdfViewerPlugin.swift @@ -26,6 +26,10 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { getImage(call:call,result:result) } + else if(call.method == "getTileImage") + { + getTileImage(call: call, result: result) + } else if(call.method == "getPagesWidth") { getPagesWidth(call:call,result:result) @@ -69,11 +73,11 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { guard let argument = call.arguments else {return} let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let document = self.documentRepo[documentID] else{return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesWidth = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) + let page = document!.page(at: Int(index)) var pageRect = page!.getBoxRect(.cropBox) if(page!.rotationAngle > 0) { @@ -90,11 +94,11 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { { guard let argument = call.arguments else {return} let documentID = argument as! String - let document = self.documentRepo[documentID]!! - let pageCount = NSNumber(value: document.numberOfPages) + guard let document = self.documentRepo[documentID] else{return} + let pageCount = NSNumber(value: document!.numberOfPages) var pagesHeight = Array() for index in stride(from: 1,to: pageCount.intValue + 1, by: 1){ - let page = document.page(at: Int(index)) + let page = document!.page(at: Int(index)) var pageRect = page!.getBoxRect(.cropBox) if(page!.rotationAngle > 0) { @@ -118,18 +122,22 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { scale = 2 } let documentID = args!["documentID"] as! String - result(getImageForPlugin(index: index!,scale: scale,documentID: documentID)) + guard let image = getImageForPlugin(index: index!,scale: scale,documentID: documentID) else { + result(FlutterStandardTypedData()) + return + } + result(image) } // Gets the image for plugin - private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData + private func getImageForPlugin(index: Int,scale: CGFloat,documentID: String) -> FlutterStandardTypedData? { - let document = self.documentRepo[documentID]!! - let page = document.page(at: Int(index)) - var pageRect = page!.getBoxRect(.cropBox) + guard let document = self.documentRepo[documentID] else {return nil} + let page = document!.page(at: Int(index)) + let pageRect = page!.getBoxRect(.cropBox) let imageRect = CGRect(x: 0,y: 0,width: pageRect.size.width*CGFloat(scale),height: pageRect.size.height*CGFloat(scale)) let nsImage = NSImage(size: imageRect.size, actions: { cgContext in - let transform = page!.getDrawingTransform(.cropBox, rect: pageRect, rotate: 0, preserveAspectRatio: true) + let transform = page!.getDrawingTransform(.cropBox, rect: CGRect(origin: CGPoint.zero, size: pageRect.size), rotate: 0, preserveAspectRatio: true) cgContext.translateBy(x: 0.0, y: imageRect.size.height) cgContext.scaleBy(x: CGFloat(scale), y: -CGFloat(scale)) cgContext.concatenate(transform) @@ -139,6 +147,60 @@ public class SyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { let bytes = nsImage.tiffRepresentation?.bitmap?.png return bytes == nil ? FlutterStandardTypedData() : FlutterStandardTypedData(bytes: bytes!) } + + // Gets the pdf page image from the specified page + private func getTileImage( call: FlutterMethodCall, result: @escaping FlutterResult) + { + guard let argument = call.arguments else {return} + let args = argument as? [String: Any] + let pageNumber = args!["pageNumber"] as? Int + let scale = CGFloat(args!["scale"] as! Double) + let width = args!["width"] as! Double + let height = args!["height"] as! Double + let x = args!["x"] as! Double + let y = args!["y"] as! Double + + let documentID = args!["documentID"] as! String + guard let tileImage = getTileImageForPlugin(pageNumber: pageNumber!, scale: scale, + width: width, height: height, x: x, y: y, documentID: documentID) + else { + result(FlutterStandardTypedData()) + return + } + result(tileImage) + } + + // Gets the image for plugin + private func getTileImageForPlugin(pageNumber: Int, scale: CGFloat, width: Double, height: Double, x: Double, y: Double, documentID: String) -> FlutterStandardTypedData? + { + guard let document = self.documentRepo[documentID] else {return nil} + let page = document!.page(at: Int(pageNumber)) + let pageRect = page!.getBoxRect(.cropBox) + + var pageWidth = pageRect.width + var pageHeight = pageRect.height + + if(page!.rotationAngle == 90 || page!.rotationAngle == 270) { + pageWidth = pageRect.height + pageHeight = pageRect.width + } + let imageRect = CGRect(x: 0,y: 0, width: width, height: height) + let bounds = CGRect(x: -(pageWidth * scale / 2) + (pageWidth / 2) - CGFloat(x), + y: -(pageHeight * scale / 2) + (pageHeight / 2) + CGFloat(y), + width: pageWidth * scale, height: pageHeight * scale) + let nsImage = NSImage(size: imageRect.size, actions: { cgContext in + let transform = page!.getDrawingTransform(.cropBox, rect: bounds, rotate: 0, preserveAspectRatio: true) + cgContext.translateBy(x: 0.0, y: pageHeight * scale) + cgContext.scaleBy(x: scale, y: -scale) + cgContext.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + cgContext.fill(bounds) + cgContext.concatenate(transform) + cgContext.drawPDFPage(page!) + cgContext.endPage() + }) + let bytes = nsImage.tiffRepresentation?.bitmap?.png + return bytes == nil ? FlutterStandardTypedData() : FlutterStandardTypedData(bytes: bytes!) + } } extension NSBitmapImageRep { diff --git a/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml b/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml index a8c45a74c..c3d5c4ffe 100644 --- a/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml +++ b/packages/syncfusion_pdfviewer_platform_interface/analysis_options.yaml @@ -1,3 +1,5 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + analyzer: errors: lines_longer_than_80_chars: ignore diff --git a/packages/syncfusion_pdfviewer_platform_interface/example/pubspec.yaml b/packages/syncfusion_pdfviewer_platform_interface/example/pubspec.yaml index 8edadab51..7e9260272 100644 --- a/packages/syncfusion_pdfviewer_platform_interface/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_platform_interface/example/pubspec.yaml @@ -20,10 +20,10 @@ dependencies: syncfusion_flutter_pdfviewer: git: - url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer + url: https://SyncfusionBuild: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@github.com/essential-studio/flutter-pdfviewer path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: release/24.1.1 - ref: release/24.1.1 + branch: release/25.1.1 + ref: release/25.1.1 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart b/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart index 41a203c13..bd71c47ee 100644 --- a/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart +++ b/packages/syncfusion_pdfviewer_platform_interface/lib/src/method_channel_pdfviewer.dart @@ -39,6 +39,21 @@ class MethodChannelPdfViewer extends PdfViewerPlatform { }); } + /// Gets the image's bytes information of the specified portion of the page + @override + Future getTileImage(int pageNumber, double currentScale, double x, + double y, double width, double height, String documentID) async { + return _channel.invokeMethod('getTileImage', { + 'pageNumber': pageNumber, + 'scale': currentScale, + 'x': x, + 'y': y, + 'width': width, + 'height': height, + 'documentID': documentID + }); + } + /// Closes the PDF document. @override Future closeDocument(String documentID) async { diff --git a/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart b/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart index f85230ae3..04a285611 100644 --- a/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart +++ b/packages/syncfusion_pdfviewer_platform_interface/lib/src/pdfviewer_platform_interface.dart @@ -56,6 +56,12 @@ abstract class PdfViewerPlatform extends PlatformInterface { throw UnimplementedError('getImage() has not been implemented.'); } + /// Gets the image's bytes information of the specified portion of the page. + Future getTileImage(int pageNumber, double scale, double x, + double y, double width, double height, String documentID) async { + throw UnimplementedError('getTileImage() has not been implemented.'); + } + /// Closes the PDF document. Future closeDocument(String documentID) async { throw UnimplementedError('closeDocument() has not been implemented.'); diff --git a/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml b/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml index 88840c53d..9af6d9c6f 100644 --- a/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_platform_interface/pubspec.yaml @@ -1,6 +1,6 @@ name: syncfusion_pdfviewer_platform_interface description: A common platform interface for the Flutter PDF Viewer library that lets you view the PDF documents seamlessly and efficiently. -version: 21.2.5 +version: 24.2.9 homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_pdfviewer_platform_interface environment: diff --git a/packages/syncfusion_pdfviewer_web/example/pubspec.yaml b/packages/syncfusion_pdfviewer_web/example/pubspec.yaml index 25563ab45..60c62cbe1 100644 --- a/packages/syncfusion_pdfviewer_web/example/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_web/example/pubspec.yaml @@ -13,7 +13,6 @@ dependencies: sdk: flutter syncfusion_flutter_pdfviewer: path: ../../syncfusion_flutter_pdfviewer - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. diff --git a/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart b/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart index 5e5551483..0a8eb4120 100644 --- a/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart +++ b/packages/syncfusion_pdfviewer_web/lib/pdfviewer_web.dart @@ -71,6 +71,60 @@ class SyncfusionFlutterPdfViewerPlugin extends PdfViewerPlatform { return Uint8List.fromList([0]); } + /// Gets the image's bytes information of the specified portion of the page. + Future getTileImage(int pageNumber, double scale, double x, + double y, double width, double height, String documentID) async { + if (_documentRepo[documentID] != null) { + PdfJsPage page = await promiseToFuture( + _documentRepo[documentID]!.getPage(pageNumber)); + PdfJsViewport viewport = page.getViewport(_settings); + return _renderPageTile(page, viewport, scale, x, y, width, height); + } + return Uint8List.fromList([0]); + } + + Future _renderPageTile( + PdfJsPage page, + PdfJsViewport viewport, + double scale, + double x, + double y, + double width, + double height, + ) async { + final html.CanvasElement htmlCanvas = + js.context['document'].createElement('canvas'); + final Object? context = htmlCanvas.getContext('2d'); + + viewport = page.getViewport(Settings() + ..offsetX = -(x * scale) + ..offsetY = -(y * scale) + ..scale = scale); + + htmlCanvas + ..height = height.toInt() + ..width = width.toInt(); + final renderSettings = Settings() + ..canvasContext = (context as html.CanvasRenderingContext2D) + ..viewport = viewport + ..annotationMode = 0; + await promiseToFuture(page.render(renderSettings).promise); + + // Renders the page as a PNG image and retrieve its byte information. + final completer = Completer(); + final blob = await htmlCanvas.toBlob(); + final bytesBuilder = BytesBuilder(); + final fileReader = html.FileReader()..readAsArrayBuffer(blob); + fileReader.onLoadEnd.listen( + (html.ProgressEvent e) { + bytesBuilder.add(fileReader.result as List); + completer.complete(); + }, + ); + await completer.future; + return bytesBuilder.toBytes(); + } + /// Closes the PDF document. @override Future closeDocument(String documentID) async { diff --git a/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart b/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart index 1f0d70201..eca1b46cf 100644 --- a/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart +++ b/packages/syncfusion_pdfviewer_web/lib/src/pdfjs.dart @@ -21,6 +21,8 @@ class Settings { external set canvasContext(CanvasRenderingContext2D value); external set viewport(PdfJsViewport value); external set annotationMode(num value); + external set offsetX(double value); + external set offsetY(double value); } @anonymous diff --git a/packages/syncfusion_pdfviewer_web/pubspec.yaml b/packages/syncfusion_pdfviewer_web/pubspec.yaml index 9fd5523f3..816c4604a 100644 --- a/packages/syncfusion_pdfviewer_web/pubspec.yaml +++ b/packages/syncfusion_pdfviewer_web/pubspec.yaml @@ -12,11 +12,10 @@ dependencies: sdk: flutter flutter_web_plugins: sdk: flutter - js: ^0.6.3 + js: ^0.7.1 meta: ^1.3.0 syncfusion_pdfviewer_platform_interface: path: ../syncfusion_pdfviewer_platform_interface - dev_dependencies: flutter_test: diff --git a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp index 02a1bb254..0e4817642 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp +++ b/packages/syncfusion_pdfviewer_windows/windows/pdfviewer.cpp @@ -19,12 +19,28 @@ namespace pdfviewer std::shared_ptr getPdfDocument(std::string docID) { - return documentRepo.find(docID)->second; + std::shared_ptr documentPtr = nullptr; + + std::map>::iterator documentRepoIterator = documentRepo.find(docID); + // Checks whether the docID is present or not in the documentRepo map + if (documentRepoIterator != documentRepo.end()) + { + documentPtr = documentRepoIterator->second; + } + else + { + documentPtr = nullptr; + } + + return documentPtr; } void closePdfDocument(std::string docID) { - FPDF_DOCUMENT document = getPdfDocument(docID)->pdfDocument; + std::shared_ptr documentPtr = getPdfDocument(docID); + if (documentPtr == nullptr) + return; + FPDF_DOCUMENT document = documentPtr->pdfDocument; FPDF_CloseDocument(document); documentRepo.erase(docID); } diff --git a/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp b/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp index 0c23f5eb1..76f6d04c1 100644 --- a/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp +++ b/packages/syncfusion_pdfviewer_windows/windows/syncfusion_pdfviewer_windows_plugin.cpp @@ -88,12 +88,17 @@ namespace pdfviewer std::unique_ptr> result) { auto id = std::get(*method_call.arguments()); - std::shared_ptr document = getPdfDocument(id); - int pageCount = FPDF_GetPageCount(document->pdfDocument); + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) + { + result->Error("Error", "Document not found"); + return; + } + int pageCount = FPDF_GetPageCount(documentPtr->pdfDocument); std::vector pageHeights; for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { - FPDF_PAGE page = FPDF_LoadPage(document->pdfDocument, pageIndex); + FPDF_PAGE page = FPDF_LoadPage(documentPtr->pdfDocument, pageIndex); double height = FPDF_GetPageHeightF(page); pageHeights.push_back(height); FPDF_ClosePage(page); @@ -107,12 +112,17 @@ namespace pdfviewer std::unique_ptr> result) { auto id = std::get(*method_call.arguments()); - std::shared_ptr document = getPdfDocument(id); - int pageCount = FPDF_GetPageCount(document->pdfDocument); + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) + { + result->Error("Error", "Document not found"); + return; + } + int pageCount = FPDF_GetPageCount(documentPtr->pdfDocument); std::vector pageWidth; for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { - FPDF_PAGE page = FPDF_LoadPage(document->pdfDocument, pageIndex); + FPDF_PAGE page = FPDF_LoadPage(documentPtr->pdfDocument, pageIndex); double width = FPDF_GetPageWidthF(page); pageWidth.push_back(width); FPDF_ClosePage(page); @@ -162,7 +172,13 @@ namespace pdfviewer auto id = std::get(documentID->second); auto scale = std::get(currentScale->second); auto index = std::get(pageIndex->second); - FPDF_DOCUMENT document = getPdfDocument(id)->pdfDocument; + std::shared_ptr documentPtr = getPdfDocument(id); + if (documentPtr == nullptr) + { + result->Error("Error", "Document not found"); + return; + } + FPDF_DOCUMENT document = documentPtr->pdfDocument; FPDF_PAGE page = FPDF_LoadPage(document, index - 1); if (scale < 1.75) { @@ -234,6 +250,95 @@ namespace pdfviewer result->Success(flutter::EncodableValue(imageData)); } + void GetPdfPageTileImage( + const flutter::MethodCall &method_call, + std::unique_ptr> result) + { + const auto *arguments = std::get_if(method_call.arguments()); + auto pageNumberArgument = arguments->find(flutter::EncodableValue("pageNumber")); + auto currentScale = arguments->find(flutter::EncodableValue("scale")); + auto documentID = arguments->find(flutter::EncodableValue("documentID")); + auto xArgument = arguments->find(flutter::EncodableValue("x")); + auto yArgument = arguments->find(flutter::EncodableValue("y")); + auto widthArgument = arguments->find(flutter::EncodableValue("width")); + auto heightArgument = arguments->find(flutter::EncodableValue("height")); + auto id = std::get(documentID->second); + auto scale = std::get(currentScale->second); + auto pageNumber = std::get(pageNumberArgument->second); + auto x = std::get(xArgument->second); + auto y = std::get(yArgument->second); + int width = (int)std::get(widthArgument->second); + int height = (int)std::get(heightArgument->second); + FPDF_DOCUMENT document = getPdfDocument(id)->pdfDocument; + FPDF_PAGE page = FPDF_LoadPage(document, pageNumber - 1); + + FS_MATRIX matrix = {(float)scale, 0, 0, (float)scale, (float)(-x * scale), (float)(-y * scale)}; + FS_RECTF rect = {0,0, (float)(width * scale), (float)(height * scale)}; + + // Create empty bitmap and render page onto it + auto bitmap = FPDFBitmap_Create(width, height, 0); + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); + FPDF_RenderPageBitmapWithMatrix(bitmap, page, &matrix, &rect, 0); + + // Convert bitmap into RGBA format + uint8_t *scanArg = static_cast(FPDFBitmap_GetBuffer(bitmap)); + auto bitmapStride = FPDFBitmap_GetStride(bitmap); + + // Convert to image format + Gdiplus::GdiplusStartupInput gdiplusStartupInput; + ULONG_PTR tokenGDI; + Gdiplus::GdiplusStartup(&tokenGDI, &gdiplusStartupInput, NULL); + + // Get the CLSID of the image encoder. + CLSID pngClsid; + GetPNGImageID(&pngClsid); + + // Create gdi+ bitmap from raw image data + auto gdiBitmap = + new Gdiplus::Bitmap(width, height, bitmapStride, PixelFormat32bppRGB, scanArg); + + // Create stream for converted image + IStream *stream = nullptr; + CreateStreamOnHGlobal(NULL, TRUE, &stream); + + // Encode image onto stream + auto gdiStatus = gdiBitmap->Save(stream, &pngClsid, NULL); + if (gdiStatus == Gdiplus::OutOfMemory) + { + throw std::exception("Image encode failed due to out of memory"); + } + else if (gdiStatus != Gdiplus::Ok) + { + throw std::exception("Image endode failed"); + } + + // Get raw memory of stream + HGLOBAL global = NULL; + GetHGlobalFromStream(stream, &global); + + // copy IStream to buffer + size_t bufferSize = GlobalSize(global); + std::vector imageData; + imageData.resize(bufferSize); + + // lock & unlock memory + LPVOID lockImage = GlobalLock(global); + memcpy(&imageData[0], lockImage, bufferSize); + GlobalUnlock(global); + + // Close stream + stream->Release(); + + // Cleanup gid+ + delete gdiBitmap; + Gdiplus::GdiplusShutdown(tokenGDI); + + FPDFBitmap_Destroy(bitmap); + FPDF_ClosePage(page); + + result->Success(flutter::EncodableValue(imageData)); + } + void SyncfusionPdfviewerWindowsPlugin::HandleMethodCall( const flutter::MethodCall &method_call, std::unique_ptr> result) @@ -254,6 +359,10 @@ namespace pdfviewer { GetPdfPageImage(method_call, std::move(result)); } + else if (method_call.method_name().compare("getTileImage") == 0) + { + GetPdfPageTileImage(method_call, std::move(result)); + } else if (method_call.method_name().compare("closeDocument") == 0) { auto id = std::get(*method_call.arguments());